AI 生成摘要
本文分享了一道原题设为拓扑排序但实际使用广度优先搜索(BFS)进行暴力求解的解题经验。文章详细讲解了如何通过模拟手动计算分数的加减乘除及约分,并列出相关代码片段。作者指出利用乘法分配律将多股水流分开计算最后求和即可避免复杂依赖关系。最终代码虽因超时卡点(`unsigned long long`)仅获 80 分,但因测试数据较水而 AC。文末还提及了非标准类型 `__int128` 在新竞赛中的应用。

首先一读完题,就觉得是拓扑排序,然而我基本上不会写拓扑(悲)

所以,爆搜!启动!

出乎意料的是竟然没有 TLE ,而是因为爆 unsigned long long WA 了两个点。光荣地得了 80pts!

解题思路

已经说过了,爆搜。

当然,是有技巧的。不然写着很累。

分数运算

题目要求输出分数,比较麻烦,所以我们先解决分数运算

首先,我们用 $p$ 和 $q$ 分别存一个分数的分子和分母。

然后实现约分,具体方法为直接模拟,上过小学的都会。最小公倍数可以用 STL 中的 __gcd 函数:

int gcd = __gcd(p, q);
p /= gcd, q /= gcd;

然后是除法,根据小学学过的 $\frac{a}{b} / \frac{c}{d} = \frac{a}{b} * \frac{d}{c} = \frac{a * d}{b * c}$ 。又因为是分成若干份,所以一定是除以一个整数,也就是说 $d = 1$ 。所以我们只需要把分母 q 乘上要分成的份数,再约分就可以了。

q *= e[x].size();
int gcd = __gcd(p, q);
p /= gcd, q /= gcd;

最后我们再来写最烦的加法。根据小学学过的分数的运算先通分,然后分子相加,最后约分,就好了,写起来还是比较麻烦的。

int gcd = __gcd(qq, q);
int lcm = qq * q / gcd;
pp = pp * (lcm / qq) + p * (lcd / q);
qq = lcm;
gcd = __gcd(pp,qq);
pp /= gcd;
qq /= gcd;

爆搜

大部分人写的应该都是电风扇,但是我写了 bfs。

首先把几个起点全部加入队列,虽然题目写的是会几股水合成一股的,不太好写,但是根据乘法分配律(前面说过了,除以一个数可以当成乘以它的倒数)与加法结合律,我们可以证明每股分开来算,在最后加起来不会影响结果。

80pts code

#include <bits/stdc++.h>
#define int unsigned long long
using namespace std;
const int N = 1e5 + 5;
struct Node
{
	int x, p, q;
};
struct Point
{
	int p, q;
} ans[N];
int n, m, cnt[N];
vector<int> e[N];
void bfs()
{
	queue<Node> Q;
	for (int i = 1; i <= n; i++)
	{
		if (cnt[i] == 0)
			Q.push({i, 1, 1});
	}
	while (!Q.empty())
	{
		Node now = Q.front();
		Q.pop();
		int x = now.x, p = now.p, q = now.q;
		if (e[x].size() == 0)
		{
			if (ans[x].p == 0 && ans[x].q == 0)
				ans[x].p = p, ans[x].q = q;
			else
			{
				int gcd = __gcd(ans[x].q, q);
				int lcm = ans[x].q * q / gcd;
				ans[x].p = ans[x].p * (lcm / ans[x].q) + p * (lcm / q);
				ans[x].q = lcm;
				gcd = __gcd(ans[x].p, ans[x].q);
				ans[x].p /= gcd;
				ans[x].q /= gcd;
			}
			continue;
		}
		q *= e[x].size();
		int gcd = __gcd(p, q);
		p /= gcd, q /= gcd;
		for (auto i : e[x])
		{
			Q.push({i, p, q});
		}
	}
}
signed main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		int d;
		cin >> d;
		for (int j = 1; j <= d; j++)
		{
			int a;
			cin >> a;
			e[i].push_back(a);
			cnt[a]++;
		}
	}
	bfs();
	for (int i = 1; i <= n; i++)
	{
		if (e[i].size() == 0)
		{
			int gcd = __gcd(ans[i].p, ans[i].q);
			ans[i].p /= gcd;
			ans[i].q /= gcd;
			cout << ans[i].p << " " << ans[i].q << endl;
		}
	}
}

unsigned long long WA 了两个点,要么开__128 要么用高精度。

AC code(__int128)

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
struct Node
{
	__int128 x, p, q;
};
struct Point
{
	__int128 p, q;
} ans[N];
int n, m, cnt[N];
vector<int> e[N];
void bfs()
{
	queue<Node> Q;
	for (int i = 1; i <= n; i++)
	{
		if (cnt[i] == 0)
			Q.push({i, 1, 1});
	}
	while (!Q.empty())
	{
		Node now = Q.front();
		Q.pop();
		__int128 x = now.x, p = now.p, q = now.q;
		if (e[x].size() == 0)
		{
			if (ans[x].p == 0 && ans[x].q == 0)
				ans[x].p = p, ans[x].q = q;
			else
			{
				__int128 gcd = __gcd(ans[x].q, q);
				__int128 lcm = ans[x].q * q / gcd;
				ans[x].p = ans[x].p * (lcm / ans[x].q) + p * (lcm / q);
				ans[x].q = lcm;
				gcd = __gcd(ans[x].p, ans[x].q);
				ans[x].p /= gcd;
				ans[x].q /= gcd;
			}
			continue;
		}
		q *= e[x].size();
		int gcd = __gcd(p, q);
		p /= gcd, q /= gcd;
		for (auto i : e[x])
		{
			Q.push({i, p, q});
		}
	}
}
void print(__int128 x) // __int128输出
{
    if(x < 0)
	{
        putchar('-');
        x = -x;
    }
    if(x > 9)
        print(x / 10);
    putchar(x % 10 + '0');
}
signed main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		int d;
		cin >> d;
		for (int j = 1; j <= d; j++)
		{
			int a;
			cin >> a;
			e[i].push_back(a);
			cnt[a]++;
		}
	}
	bfs();
	for (int i = 1; i <= n; i++)
	{
		if (e[i].size() == 0)
		{
			int gcd = __gcd(ans[i].p, ans[i].q);
			ans[i].p /= gcd;
			ans[i].q /= gcd;
			print(ans[i].p);
			cout << " ";
			print(ans[i].q);
			cout << endl;
		}
	}
}

hack 数据

虽然但是,爆搜时间复杂度是指数级的 (可能是 $O(nmd)$ ?) 的,本来只能拿 30 分但是数据太水居然放过了,只要让每个点的出边都是 5 条就可以轻松卡掉,hack 数据(数据由123456zmy)提供。

关于__int128

当时__int128是无法使用的,而现在__int128 在 NOI 系列赛和 CSP-J/S 等活动中已经解禁了。

尽管它不是 C++标准的一部分,但在某些编译器(如 GCC、G++)的64 位版本中得到了支持。根据 NOI 官网的文章:NOI Linux 2.0 发布,将于 9 月 1 日起正式启用!,该系统自 2021 年 9 月 1 日起作为 NOI 系列比赛和 CSP-J/S 等活动的标准环境使用。

根据 NOI Linux 2.0 的**“系统情况简表”**,系统为 64 位并且使用 G++9.3.0 作为 C++编译器。所以万能头文件及 __int128 全面解禁!!!