NOIP 2020 T1 排水系统
题解
- 很显然是拓扑排序,按题意直接模拟复杂度仅仅是
O
(
n
)
O(n)
O(n)的。
- 但是涉及到分数的加法,通分会爆变量范围吗?
- 一开始以为最大只是
5
11
∗
10
5^{11}*10
511∗10,连int都不会爆,保险起见还是开了long long。
- 这样其实是错误的,不能只考虑一条链上,三条深度为
11
11
11的链,在最后的位置合并,分母最大可以到
(
3
∗
4
∗
5
)
11
=
6
0
11
≈
2
65
(3*4*5)^{11}=60^{11}≈2^{65}
(3∗4∗5)11=6011≈265,long long都已经无法承受。
- 如果开了long long,且分数相加时分母取LCM先除后乘,可以得到
90
90
90分;
- 如果先乘后除,或者分数相加时分母直接相乘, 只可以得到
60
60
60分。
- 想通过这道题,可以写一个两位数的高精度,或者用long double,注意运算时的精度。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
#define N 100010
#define M 500010
#define ld long double
#define e 0.000000000001
int last[N], nxt[M * 2], to[M * 2], len = 0;
int d0[N], d[N];
int q[N];
struct {
ld x, y;
}s[N];
void add(int x, int y) {
to[++len] = y;
nxt[len] = last[x];
last[x] = len;
}
ld fl(ld x) {
ld y = floor(x);
if(y + 1.0 - e <= x) y++;
return y;
}
ld gcd(ld x, ld y) {
return (y >= -e && y <= e) ? x : gcd(y, x - fl(x / y) * y);
}
int main() {
int n, m, i, j, x, y;
scanf("%d%d", &n, &m);
for(i = 1; i <= n; i++) {
scanf("%d", &d0[i]);
for(j = 1; j <= d0[i]; j++) scanf("%d", &x), d[x]++, add(i, x);
}
int l = 0, r = 0;
for(i = 1; i <= n; i++) s[i].y = 1;
for(i = 1; i <= n; i++) if(!d[i]) q[++r] = i, s[i].x = 1;
int tt = 0;
while(l < r) {
x = q[++l];
for(int i = last[x]; i; i = nxt[i]) {
y = to[i];
ld X = s[x].x, Y = s[x].y * d0[x];
ld g = gcd(s[y].y, Y);
s[y].x = s[y].y / g * X + Y / g * s[y].x;
s[y].y = s[y].y / g * Y;
d[y]--;
if(!d[y]) q[++r] = y;
}
}
for(i = 1; i <= n; i++) if(!d0[i]) {
ld g = gcd(s[i].x, s[i].y);
printf("%.0Lf %.0Lf
", s[i].x / g, s[i].y / g);
}
return 0;
}
自我小结
- 考虑问题时还不够全面,同时写代码没有注意细节差异,所谓“差不多”差距可能很大。
- 需要日常训练积累经验,避免细节大丢分的情况。