NOIP2020
T1:排水系统
题意:n 个排水节点(1 ... n),m 个接水口(1 ... m),0 个排水管道的为出水口。
输入样例:
5 1
3 2 3 5
2 4 5
2 5 4
0
0
输出样例:
1 3
2 3
画出样例数据当中的示意图,如下图所示:

1、确定入水口:根据题意可知,有m个入水口,那么具体的口是(1,2,... ,m)
2、确定出水口:根据题意可知,在输入数据当中,如果没有排出管道连到其他节点,那么即为出水口。根据输入为0的时候,用一个数组标记即可。
3、在模拟流到其他节点的时候,怎么恰当的表示数据?肯定不能每次都用小数算出来,这样会有数精度的损失。那么根据流出管道的数量size,只要每次乘以一个size,即可得分流到该节点的水流量。这时候分为两种情况:
1)假如是中间节点的话,那么只要不停的乘以这个size得到当前节点分母的大小,分子就一直都是1。然后加入队列。
2)如果是最终流出节点的话,那么就每次从队列里取出一个节点之后,若该节点的下一个节点就是流出节点的话,那么加上第一个流入该节点的流量;若后面还有节点流到该流出节点的话,那么就累加起来:得到最终的ans_x[i], ans_y[i]。两个分数相加的简单方法就是先用最大公约数求出最小公倍数,然后最小公倍数除以各自的分母即为两个分子要乘的积。加起来之后再用最大公约数约一下分得到最简分数。
因为本题的数据范围是:可能会到10-26,所以即使是long long也过不了。可以用double,然后对应的gcd改成 a - floor(a / b) * b即可。
AC代码:
#include <cstdio>
#include <vector>
#include <queue>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 1e5 + 10;
const double eps = 1e-18;
#define int long long
int n, m;
double x[N], y[N], G;
struct Node{
int num, val;
}b[N];
bool st[N];
vector<int> a[N];
queue<Node> q;
double gcd(double a, double b)
{
if(a < b) swap(a, b);
if(b < eps) return a;
return gcd(b, a - floor(a / b) * b);
}
void bfs()
{
while(q.size())
{
Node t = q.front(); q.pop();
int Size = (int)a[t.num].size();
t.val *= Size;
for(int i = 0; i < Size; i ++)
{
int Y = a[t.num][i];
if(st[Y])
{
if(!x[Y] || !y[Y])
{
x[Y] = 1; y[Y] = t.val;
continue;
}
double g1 = gcd(y[Y], t.val), g = y[Y] / g1 * t.val;
x[Y] = g / y[Y] * x[Y] + g / t.val * 1;
y[Y] = g;
G = gcd(x[Y], y[Y]);
x[Y] /= G;
y[Y] /= G;
continue;
}
q.push({Y, t.val});
}
}
}
signed main()
{
scanf("%lld%lld", &n, &m);
for(int i = 1; i <= n; i ++)
{
int T; scanf("%lld", &T);
if(!T) { st[i] = 1; continue; }
while(T --)
{
int x; scanf("%lld", &x);
a[i].push_back(x);
}
}
for(int i = 1; i <= m; i ++)
{
Node t; t.num = i; t.val = 1;
q.push(t);
}
bfs();
for(int i = 1; i <= n; i ++)
if(st[i])
printf("%.0lf %.0lf\n", x[i], y[i]);
}

浙公网安备 33010602011771号