[题解]NOIP2016提高组の题解集合 - by xyz32768
2017-11-04 18:11
288 查看
NOIP2016提高组的难度参差不齐……按照难度从小到大排序讲吧……
代码:
考虑状态压缩。先预处理出con[i][j],表示第i只猪和第j只猪与(0,0)组成的抛物线,储存该抛物线能消灭猪的集合(用2进制表示,一位为1表示不能消灭这个猪,为0则可以消灭这个猪)。
预处理较简单,注意两个猪(x1,y1)(x2,y2)组成的抛物线的解析式为:
y=x1y2−x2y1x1x2(x2−x1)x2+x22y1−x21y2x1x2(x2−x1)x
然后设f[S]为当前猪的状态为二进制S(为1则未消灭,为0则已消灭)。边界即f[0]=0。
转移有两种:
1、f[S]=min(f[S],f[S&con[i][j]]+1),i,j∈S,i≠j
2、f[S]=min(f[S],f[S−2i−1]+1),i∈S
这样就能O(2nn2)解决了。
优化:每一个猪是肯定要消灭的,所以可以在第一个转移中,用S集合中的第一个数来代替i,将复杂度优化到O(2nn)。
代码:
但是O(nlogn)的复杂度无法承受n≤7∗106的数据范围。
当q=0的时候,可以很容易得到每次切蚯蚓得到的两段是单调不增的。
但q≠0时也满足这个性质。
证明:
如果说这一次切掉的蚯蚓实际长度为x+delta,下一次切掉的蚯蚓实际长度为y+delta+q,由题意容易得到x>y。
那么这一次把蚯蚓切成的两段中,第一段的相对长度为p(x+delta)−delta−q=p∗x+p∗delta−delta−q。
下一次把蚯蚓切成的两段中,第一段相对长度为p(y+delta+q)−delta−2q=p∗y+p∗delta+p∗q−delta−2q
由0<p<1,q>=0得pq−q<0
又由于x>y,所以p(x−y)>0,即px−py>pq−q
也就是px+p∗delta−delta−q>py+p∗delta+p∗q−delta−2q。第二段同理。即每次切掉的两个蚯蚓长度是单调不增的。
根据这个特性,可以维护3个队列,分别为未切的蚯蚓,第一段和第二段。每一次操作就在3个队列的队首中选最大值出队,切完后加入到第二个队列和第三个队列的队尾,同时维护delta。输出答案后,将三个队列中剩余的元素归并即可。
代码:
DP模型:设f[i][j][0/1]为到了第i个时间段,换了j次教室,第三维为0表示不换第i个教室,否则表示换第i个教室。
转移有4种:
1、第i−1和第i个教室都不换。即
f[i][j][0]=min(f[i][j][0],f[i−1][j][0]+len[ci−1][ci])。
2、换第i−1个教室,不换第i个教室。这时候将答案分成两部分,一部分是1到i−1,第二部分是i−1到i。
这时候第一部分的期望为f[i−1][j][1],第二部分的期望为len[di−1][ci]∗ki−1+len[ci−1][ci]∗(1−ki−1)。所以:
f[i][j][0]=min(f[i][j][0],f[i−1][j][1]+len[di−1][ci]∗ki−1+len[ci−1][ci]∗(1−ki−1)。
3、不换第i−1个教室,换第i个教室。这时候也是一样的思想,即
f[i][j][1]=min(f[i][j][1],f[i−1][j−1][0]+len[ci−1][di]∗ki+len[ci−1][ci]∗(1−ki))。
4、第i−1个教室和第i个教室都换。这时候是情况最多的,有4种。分别为:
(1)两个申请都通过。即len[di−1][di],发生的概率为ki−1∗ki。
(2)只通过第一个申请。即len[di−1][ci],发生的概率为ki−1∗(1−ki)。
(3)只通过第二个申请。即len[ci−1][di],发生的概率为(1−ki−1)∗ki。
(4)两个申请都不通过。即len[ci−1][ci],发生的概率为(1−ki−1)∗(1−ki)。
得出转移:
f[i][j][1]=min(f[i−1][j−1][1],len[di−1][di]∗ki−1∗ki
+len[di−1][ci]∗ki−1∗(1−ki)+len[ci−1][di]∗(1−ki−1)∗ki
+len[ci−1][ci]∗(1−ki−1)∗(1−ki))。
代码:
可以发现,第u个观察员能观察到的玩家数目为:
在Si到lcai的路径上合法的i的个数+在lcai到Ti的路径上合法的i个数,如果lcai算了两遍则减1。
怎么求前两个内容呢?
先考虑第一个,设u的深度为dep[u],那么对于一个点v往上走,点v在恰好Wu的时间经过u的条件是dep[u]+Wu=dep[v]。但是可以发现,对于任意一个玩家i,Si一旦走到了lcai就不再往上走了。所以这时候的结果就是所有满足dep[u]+Wu=dep[Si]并且lcai不在u的子树内(不包括u),Si在u的子树内(包括u)的i个数。
第二个内容也是差不多的思想。对于一个玩家i,在部分路径lcai到Ti中恰好第Wu秒经过点u的条件是dep[Si]+dep[u]−2dep[lcai]=Wu,移项得dep[u]−Wu=2dep[lcai]−dep[Si]。同样,Si一旦走到了lcai就不再往上走。所以求的就是满足dep[u]−Wu=2dep[lcai]−dep[Si]并且lcai不在u的子树内(不包括u),Ti在u的子树内(包括u)的i个数。
考虑怎样实现。对于第一个内容,容易想到用一个计数器cnt[x]来维护到当前考虑过的所有Si中,满足dep[Si]=x的i的个数。但是现在只想统计当前子树内的Si,怎么办呢?可以发现,对于每一个节点,只需要去考虑深度为dep[u]+Wu的节点。所以,使用一次dfs来解决这个问题,搜到一个节点的时候u,先记下当前的cnt[dep[u]+Wu],然后再去搜u的子节点,求出cnt[dep[u]+Wu]相对于搜u的所有子节点之前的变化量(增量),就是u的子树内满足dep[u]+Wu=dep[Si]的i的个数。再考虑消除lcai的影响。因为如果lcai在u的子树内(不包括u),那么lcai一定也在u的父亲的子树内,所以对于这一部分的答案,直接从子节点传上来即可。但在子节点v的结果往父亲u上传时,如果v是某些lcai,那么传到u时就要消除因lca造成的影响,也就是说,对于所有dep[v]+Wv=dep[Si]并且lcai=v的i,在传到u时必须去掉。解决办法就是维护一个vector或邻接表,储存v作为哪些玩家路径的lca,这样就能方便地往上传了。
第二个内容的实现也是差不多,也是用dfs和增量维护子树内的信息,用vector或邻接表进行去重。但是注意细节:dep[u]−Wu有可能是负数,因此统计数组的下标要加上一个定值。
复杂度:如果用倍增求lca,那么复杂度为O(nlogn),如果用Tarjan离线求lca,那么复杂度为O(n)。
代码:
Top 6:D1T1 玩具谜题(toy) - 模拟
模拟题。根据小人的朝向和向左/右数来判断走向。注意边界。#include <cmath> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; inline int read() { int res = 0; bool bo = 0; char c; while (((c = getchar()) < '0' || c > '9') && c != '-'); if (c == '-') bo = 1; else res = c - 48; while ((c = getchar()) >= '0' && c <= '9') res = (res << 3) + (res << 1) + (c - 48); return bo ? ~res + 1 : res; } const int N = 1e5 + 5; int n, m, v ; string s ; int main() { //freopen("toy.in", "r", stdin); //freopen("toy.out", "w", stdout); int i, now = 0; n = read(); m = read(); for (i = 1; i <= n; i++) v[i] = read(), cin >> s[i]; for (i = 1; i <= m; i++) if (v[now + 1] ^ read()) now = (now + read()) % n; else now = (now - read() + n) % n; cout << s[now + 1] << endl; return 0; }
Top 5:D2T1 组合数问题(problem) - 组合数+前缀和
首先使用公式Cmn=Cmn−1+Cm−1n−1预处理2000以内的组合数取模k的值,判断一下每个值是否为0后再求一下前缀和,就能O(1)一组询问了。代码:
#include <cmath> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; inline int read() { int res = 0; bool bo = 0; char c; while (((c = getchar()) < '0' || c > '9') && c != '-'); if (c == '-') bo = 1; else res = c - 48; while ((c = getchar()) >= '0' && c <= '9') res = (res << 3) + (res << 1) + (c - 48); return bo ? ~res + 1 : res; } const int N = 2000; int m, n, PYZ, c[N + 5][N + 5], col[N + 5][N + 5], cnt[N + 5][N + 5]; int main() { //freopen("problem.in", "r", stdin); //freopen("problem.out", "w", stdout); int i, j, T; T = read(); PYZ = read(); for (i = 0; i <= N; i++) c[i][0] = 1; for (i = 1; i <= N; i++) for (j = 1; j <= i; j++) c[i][j] = (c[i - 1][j - 1] + c[i - 1][j]) % PYZ; for (j = 1; j <= N; j++) for (i = 1; i <= (N - j + 1); i++) col[j][i] = col[j][i - 1] + (c[i + j - 1][j] == 0); for (i = 1; i <= N; i++) for (j = 1; j <= i; j++) cnt[i][j] = cnt[i][j - 1] + col[j][i - j + 1]; while (T--) { m = read(); n = read(); printf("%d\n", cnt[m][min(m, n)]); } return 0; }
Top 4:D2T3 愤怒的小鸟(angrybirds) - 状压DP
首先知道,三个不在一条直线上的点可以确定一条抛物线。而(0,0)是已经固定的点,所以只要枚举两个点,就可以得到一条抛物线。考虑状态压缩。先预处理出con[i][j],表示第i只猪和第j只猪与(0,0)组成的抛物线,储存该抛物线能消灭猪的集合(用2进制表示,一位为1表示不能消灭这个猪,为0则可以消灭这个猪)。
预处理较简单,注意两个猪(x1,y1)(x2,y2)组成的抛物线的解析式为:
y=x1y2−x2y1x1x2(x2−x1)x2+x22y1−x21y2x1x2(x2−x1)x
然后设f[S]为当前猪的状态为二进制S(为1则未消灭,为0则已消灭)。边界即f[0]=0。
转移有两种:
1、f[S]=min(f[S],f[S&con[i][j]]+1),i,j∈S,i≠j
2、f[S]=min(f[S],f[S−2i−1]+1),i∈S
这样就能O(2nn2)解决了。
优化:每一个猪是肯定要消灭的,所以可以在第一个转移中,用S集合中的第一个数来代替i,将复杂度优化到O(2nn)。
代码:
#include <cmath> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; inline int read() { int res = 0; bool bo = 0; char c; while (((c = getchar()) < '0' || c > '9') && c != '-'); if (c == '-') bo = 1; else res = c - 48; while ((c = getchar()) >= '0' && c <= '9') res = (res << 3) + (res << 1) + (c - 48); return bo ? ~res + 1 : res; } const int N = 20; const double eps = 1e-9; int n, f[1 << N], ef ; double X , Y ; struct cyx { double a, b; cyx() {} cyx(double _a, double _b) : a(_a), b(_b) {} }; cyx quad(double x1, double y1, double x2, double y2) { if (abs(x1 * x2 * (x2 - x1)) < eps) return cyx(1e20, 1e20); double a = (x1 * y2 - x2 * y1) / (x1 * x2 * (x2 - x1)), b = (y1 * x2 * x2 - y2 * x1 * x1) / (x1 * x2 * (x2 - x1)); return a < -eps ? cyx(a, b) : cyx(1e20, 1e20); } int Dp(int state) { if (f[state] != -1) return f[state]; if (!state) return 0; int i, fir, res = n; for (i = 1; i <= n; i++) if ((state >> i - 1) & 1) {fir = i; break;} for (i = 1; i <= n; i++) if (((state >> i - 1) & 1)) { if (i != fir && (state & ef[fir][i]) != state) res = min(res, Dp(state & ef[fir][i]) + 1); res = min(res, Dp(state ^ (1 << i - 1)) + 1); } return f[state] = res; } void work() { int i, j, k; n = read(); read(); memset(f, -1, sizeof(f)); for (i = 1; i <= n; i++) scanf("%lf%lf", &X[i], &Y[i]); for (i = 1; i <= n; i++) for (j = i + 1; j <= n; j++) ef[i][j] = (1 << n) - 1; for (i = 1; i <= n; i++) for (j = i + 1; j <= n; j++) { cyx q = quad(X[i], Y[i], X[j], Y[j]); if (q.a > 1e19) continue; for (k = 1; k <= n; k++) if (abs(q.a * X[k] * X[k] + q.b * X[k] - Y[k]) < eps) ef[i][j] ^= 1 << k - 1; } printf("%d\n", Dp((1 << n) - 1)); } int main() { //freopen("angrybirds.in", "r", stdin); //freopen("angrybirds.out", "w", stdout); int T = read(); while (T--) work(); return 0; }
Top 3:D2T2 蚯蚓(earthworm) - 队列
容易想到用堆维护蚯蚓长度,对于蚯蚓的增长,就用一个delta标记。但是O(nlogn)的复杂度无法承受n≤7∗106的数据范围。
当q=0的时候,可以很容易得到每次切蚯蚓得到的两段是单调不增的。
但q≠0时也满足这个性质。
证明:
如果说这一次切掉的蚯蚓实际长度为x+delta,下一次切掉的蚯蚓实际长度为y+delta+q,由题意容易得到x>y。
那么这一次把蚯蚓切成的两段中,第一段的相对长度为p(x+delta)−delta−q=p∗x+p∗delta−delta−q。
下一次把蚯蚓切成的两段中,第一段相对长度为p(y+delta+q)−delta−2q=p∗y+p∗delta+p∗q−delta−2q
由0<p<1,q>=0得pq−q<0
又由于x>y,所以p(x−y)>0,即px−py>pq−q
也就是px+p∗delta−delta−q>py+p∗delta+p∗q−delta−2q。第二段同理。即每次切掉的两个蚯蚓长度是单调不增的。
根据这个特性,可以维护3个队列,分别为未切的蚯蚓,第一段和第二段。每一次操作就在3个队列的队首中选最大值出队,切完后加入到第二个队列和第三个队列的队尾,同时维护delta。输出答案后,将三个队列中剩余的元素归并即可。
代码:
#include <cmath> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; inline int read() { int res = 0; bool bo = 0; char c; while (((c = getchar()) < '0' || c > '9') && c != '-'); if (c == '-') bo = 1; else res = c - 48; while ((c = getchar()) >= '0' && c <= '9') res = (res << 3) + (res << 1) + (c - 48); return bo ? ~res + 1 : res; } const int N = 1e5 + 5, M = 7e6 + 5, INF = 0x3f3f3f3f; int n, m, q, u, v, t, a , Q[4][M + N], H[4], T[4], add; double p; int pop() { int i, id = 0, ans = -INF; for (i = 1; i <= 3; i++) if (H[i] < T[i] && Q[i][H[i] + 1] > ans) id = i, ans = Q[id][H[id] + 1]; return Q[id][++H[id]]; } int main() { //freopen("earthworm.in", "r", stdin); //freopen("earthworm.out", "w", stdout); int i, cnt = 0; n = read(); m = read(); q = read(); u = read(); v = read(); t = read(); p = (1.0 * u) / (1.0 * v); for (i = 1; i <= n; i++) a[i] = read(); sort(a + 1, a + n + 1); for (i = n; i >= 1; i--) Q[1][++T[1]] = a[i]; for (i = 1; i <= m; i++) { int x = pop() + add, y = (int) (1.0 * x * p), z = x - y; add += q; y -= add; z -= add; Q[2][++T[2]] = y; Q[3][++T[3]] = z; if ((++cnt) == t) printf("%d ", x), cnt = 0; } printf("\n"); cnt = 0; for (i = 1; i <= n + m; i++) { int x = pop() + add; if ((++cnt) == t) printf("%d ", x), cnt = 0; } printf("\n"); return 0; }
Top 2:D1T3 换教室(classroom) - Floyd+期望DP
首先使用Floyd求出任意两点之间的最短路,设i到j的最短路为len[i][j]。DP模型:设f[i][j][0/1]为到了第i个时间段,换了j次教室,第三维为0表示不换第i个教室,否则表示换第i个教室。
转移有4种:
1、第i−1和第i个教室都不换。即
f[i][j][0]=min(f[i][j][0],f[i−1][j][0]+len[ci−1][ci])。
2、换第i−1个教室,不换第i个教室。这时候将答案分成两部分,一部分是1到i−1,第二部分是i−1到i。
这时候第一部分的期望为f[i−1][j][1],第二部分的期望为len[di−1][ci]∗ki−1+len[ci−1][ci]∗(1−ki−1)。所以:
f[i][j][0]=min(f[i][j][0],f[i−1][j][1]+len[di−1][ci]∗ki−1+len[ci−1][ci]∗(1−ki−1)。
3、不换第i−1个教室,换第i个教室。这时候也是一样的思想,即
f[i][j][1]=min(f[i][j][1],f[i−1][j−1][0]+len[ci−1][di]∗ki+len[ci−1][ci]∗(1−ki))。
4、第i−1个教室和第i个教室都换。这时候是情况最多的,有4种。分别为:
(1)两个申请都通过。即len[di−1][di],发生的概率为ki−1∗ki。
(2)只通过第一个申请。即len[di−1][ci],发生的概率为ki−1∗(1−ki)。
(3)只通过第二个申请。即len[ci−1][di],发生的概率为(1−ki−1)∗ki。
(4)两个申请都不通过。即len[ci−1][ci],发生的概率为(1−ki−1)∗(1−ki)。
得出转移:
f[i][j][1]=min(f[i−1][j−1][1],len[di−1][di]∗ki−1∗ki
+len[di−1][ci]∗ki−1∗(1−ki)+len[ci−1][di]∗(1−ki−1)∗ki
+len[ci−1][ci]∗(1−ki−1)∗(1−ki))。
代码:
#include <cmath> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; inline int read() { int res = 0; bool bo = 0; char c; while (((c = getchar()) < '0' || c > '9') && c != '-'); if (c == '-') bo = 1; else res = c - 48; while ((c = getchar()) >= '0' && c <= '9') res = (res << 3) + (res << 1) + (c - 48); return bo ? ~res + 1 : res; } void chkmin(double &a, double b) { a = min(a, b); } const int N = 2005, M = 305, INF = 0x3f3f3f3f; int n, m, v, e, c , d , ex[M][M]; double K , f [2]; void floyd() { int i, j, k; for (k = 1; k <= v; k++) for (i = 1; i <= v; i++) for (j = 1; j <= v; j++) ex[i][j] = min(ex[i][j], ex[i][k] + ex[k][j]); for (i = 1; i <= v; i++) ex[i][i] = 0; } double solve() { floyd(); int i, j; for (i = 1; i <= n; i++) for (j = 0; j <= m; j++) f[i][j][0] = f[i][j][1] = 1e20; f[1][0][0] = f[1][1][1] = 0; for (i = 2; i <= n; i++) for (j = 0; j <= min(i, m); j++) { chkmin(f[i][j][0], f[i - 1][j][0] + ex[c[i - 1]][c[i]]); if (j == 0) continue; chkmin(f[i][j][0], f[i - 1][j][1] + 1.0 * ex[d[i - 1]][c[i]] * K[i - 1] + 1.0 * ex[c[i - 1]][c[i]] * (1.0 - K[i - 1])); chkmin(f[i][j][1], f[i - 1][j - 1][0] + 1.0 * ex[c[i - 1]][d[i]] * K[i] + 1.0 * ex[c[i - 1]][c[i]] * (1.0 - K[i])); chkmin(f[i][j][1], f[i - 1][j - 1][1] + 1.0 * ex[d[i - 1]][d[i]] * K[i - 1] * K[i] + 1.0 * ex[c[i - 1]][d[i]] * (1.0 - K[i - 1]) * K[i] + 1.0 * ex[d[i - 1]][c[i]] * K[i - 1] * (1.0 - K[i]) + 1.0 * ex[c[i - 1]][c[i]] * (1.0 - K[i - 1]) * (1.0 - K[i])); } double res = 1e20; for (j = 0; j <= min(n, m); j++) res = min(res, min(f [j][0], f [j][1])); return res; } int main() { //freopen("classroom.in", "r", stdin); //freopen("classroom.out", "w", stdout); int i, x, y, z; memset(ex, INF, sizeof(ex)); n = read(); m = read(); v = read(); e = read(); for (i = 1; i <= n; i++) c[i] = read(); for (i = 1; i <= n; i++) d[i] = read(); for (i = 1; i <= n; i++) scanf("%lf", &K[i]); while (e--) { x = read(); y = read(); z = read(); if (x != y) ex[x][y] = ex[y][x] = min(ex[x][y], z); } printf("%.2lf\n", solve()); return 0; }
Top 1:D1T2 天天爱跑步(running) - dfs+统计
这题是2016中最难的。首先,还是把原图视为有根树,求出每个Si和Ti的lca即lcai。可以发现,第u个观察员能观察到的玩家数目为:
在Si到lcai的路径上合法的i的个数+在lcai到Ti的路径上合法的i个数,如果lcai算了两遍则减1。
怎么求前两个内容呢?
先考虑第一个,设u的深度为dep[u],那么对于一个点v往上走,点v在恰好Wu的时间经过u的条件是dep[u]+Wu=dep[v]。但是可以发现,对于任意一个玩家i,Si一旦走到了lcai就不再往上走了。所以这时候的结果就是所有满足dep[u]+Wu=dep[Si]并且lcai不在u的子树内(不包括u),Si在u的子树内(包括u)的i个数。
第二个内容也是差不多的思想。对于一个玩家i,在部分路径lcai到Ti中恰好第Wu秒经过点u的条件是dep[Si]+dep[u]−2dep[lcai]=Wu,移项得dep[u]−Wu=2dep[lcai]−dep[Si]。同样,Si一旦走到了lcai就不再往上走。所以求的就是满足dep[u]−Wu=2dep[lcai]−dep[Si]并且lcai不在u的子树内(不包括u),Ti在u的子树内(包括u)的i个数。
考虑怎样实现。对于第一个内容,容易想到用一个计数器cnt[x]来维护到当前考虑过的所有Si中,满足dep[Si]=x的i的个数。但是现在只想统计当前子树内的Si,怎么办呢?可以发现,对于每一个节点,只需要去考虑深度为dep[u]+Wu的节点。所以,使用一次dfs来解决这个问题,搜到一个节点的时候u,先记下当前的cnt[dep[u]+Wu],然后再去搜u的子节点,求出cnt[dep[u]+Wu]相对于搜u的所有子节点之前的变化量(增量),就是u的子树内满足dep[u]+Wu=dep[Si]的i的个数。再考虑消除lcai的影响。因为如果lcai在u的子树内(不包括u),那么lcai一定也在u的父亲的子树内,所以对于这一部分的答案,直接从子节点传上来即可。但在子节点v的结果往父亲u上传时,如果v是某些lcai,那么传到u时就要消除因lca造成的影响,也就是说,对于所有dep[v]+Wv=dep[Si]并且lcai=v的i,在传到u时必须去掉。解决办法就是维护一个vector或邻接表,储存v作为哪些玩家路径的lca,这样就能方便地往上传了。
第二个内容的实现也是差不多,也是用dfs和增量维护子树内的信息,用vector或邻接表进行去重。但是注意细节:dep[u]−Wu有可能是负数,因此统计数组的下标要加上一个定值。
复杂度:如果用倍增求lca,那么复杂度为O(nlogn),如果用Tarjan离线求lca,那么复杂度为O(n)。
代码:
#include <cmath> #include <cstdio> #include <vector> #include <cstring> #include <iostream> #include <algorithm> using namespace std; inline int read() { int res = 0; bool bo = 0; char c; while (((c = getchar()) < '0' || c > '9') && c != '-'); if (c == '-') bo = 1; else res = c - 48; while ((c = getchar()) >= '0' && c <= '9') res = (res << 3) + (res << 1) + (c - 48); return bo ? ~res + 1 : res; } const int N = 3e5 + 5, LogN = 22, M = 3e5; int n, m, ecnt, nxt[N << 1], adj , go[N << 1], tim , S , T , L , dep , fa [LogN], up , down , res , cnt[N << 1], cnt_s , cnt_t , len , md; vector<int> lca_s , lca_t , s_t ; void add_edge(int u, int v) { nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v; } void dfs(int u, int fu) { dep[u] = dep[fu] + 1; fa[u][0] = fu; md = max(md, dep[u]); int i; for (i = 1; i <= 20; i++) fa[u][i] = fa[fa[u][i - 1]][i - 1]; for (int e = adj[u], v; e; e = nxt[e]) if ((v = go[e]) != fu) dfs(v, u); } int lca(int u, int v) { if (dep[u] < dep[v]) swap(u, v); int i; for (i = 20; i >= 0; i--) { if (dep[fa[u][i]] >= dep[v]) u = fa[u][i]; if (u == v) return u; } for (i = 20; i >= 0; i--) if (fa[u][i] != fa[v][i]) { u = fa[u][i]; v = fa[v][i]; } return fa[u][0]; } void dfs1(int u, int fu) { int i, cc = lca_s[u].size(), tot = cnt[up[u]]; for (int e = adj[u], v; e; e = nxt[e]) if ((v = go[e]) != fu) dfs1(v, u); cnt[dep[u]] += cnt_s[u]; if (up[u] <= md) res[u] += cnt[up[u]] - tot; for (i = 0; i < cc; i++) cnt[dep[lca_s[u][i]]]--; } void dfs2(int u, int fu) { int i, cc = lca_t[u].size(), ff = s_t[u].size(), tot = cnt[down[u] + M]; for (int e = adj[u], v; e; e = nxt[e]) if ((v = go[e]) != fu) dfs2(v, u); for (i = 0; i < ff; i++) cnt[s_t[u][i] + M]++; res[u] += cnt[down[u] + M] - tot; for (i = 0; i < cc; i++) cnt[lca_t[u][i] + M]--; } int main() { //freopen("running.in", "r", stdin); //freopen("running.out", "w", stdout); int i, x, y; n = read(); m = read(); for (i = 1; i < n; i++) { x = read(); y = read(); add_edge(x, y); add_edge(y, x); } dfs(1, 0); for (i = 1; i <= n; i++) tim[i] = read(), up[i] = dep[i] + tim[i], down[i] = dep[i] - tim[i]; for (i = 1; i <= m; i++) S[i] = read(), T[i] = read(), L[i] = lca(S[i], T[i]), cnt_s[S[i]]++, cnt_t[T[i]]++, len[i] = dep[S[i]] + dep[T[i]] - (dep[L[i]] << 1), lca_s[L[i]].push_back(S[i]), lca_t[L[i]].push_back(dep[T[i]] - len[i]), s_t[T[i]].push_back(dep[T[i]] - len[i]); dfs1(1, 0); memset(cnt, 0, sizeof(cnt)); dfs2(1, 0); for (i = 1; i <= m; i++) if (dep[S[i]] - dep[L[i]] == tim[L[i]]) res[L[i]]--; for (i = 1; i <= n; i++) printf("%d ", res[i]); printf("\n"); return 0; }
总结
蒟蒻写完这份题解的之后的第7天,NOIP2017也要开始了。祝各位神犇大佬AK NOIP,AK 省选,AK NOI,AK CTSC,AK IOI,AK ACM系列各大比赛!相关文章推荐
- [题解]NOIP2014提高组の题解集合 - by xyz32768
- [题解]NOIP2015提高组の题解集合 - by xyz32768
- NOIP2016提高组 第一天第三题 换教室 classroom 题解
- NOIP2016提高A组五校联考1总结
- NOIP2016提高组Day2
- Java提高配(三七)—–Java集合细节(三):subList的缺陷
- NOIP2012提高组day1 vigenere密码 题解
- noip2010提高组题解
- Java提高篇(三八)-----Java集合细节(四):保持compareTo和equals同步
- {题解}[jzoj4823] 【NOIP2016提高A组集训第1场10.29】小W学物理
- 2017.2.25【初中部 提高组】模拟赛B组 倒霉的小C(beats) 题解
- [题解]NOIP2017 Day2 Solution - by xyz32768
- NOIP2016提高A组集训第13场11.11 总结
- 集合中用确定的泛型限制(例如),可以提高代码执行效率
- {题解}[jzoj4882]【NOIP2016提高A组集训第12场11.10】多段线性函数
- 2017-07-08【NOIP提高组】模拟赛B组-连通块(connect)-题解
- 换教室(NOIP2016提高组Day1T3)
- codevs 1105 过河 2005年NOIP全国联赛提高组 题解(缩点方法详解)
- 【提高组NOIP2017】时间复杂度 题解 分治系统处理
- NOIP2016提高组解题报告