【bzoj2815】[ZJOI2012]灾难 拓扑排序+倍增LCA
题目描述(转自洛谷)
阿米巴是小强的好朋友。
阿米巴和小强在草原上捉蚂蚱。小强突然想,果蚂蚱被他们捉灭绝了,那么吃蚂蚱的小鸟就会饿死,而捕食小鸟的猛禽也会跟着灭绝,从而引发一系列的生态灾难。
学过生物的阿米巴告诉小强,草原是一个极其稳定的生态系统。如果蚂蚱灭绝了,小鸟照样可以吃别的虫子,所以一个物种的灭绝并不一定会引发重大的灾难。
我们现在从专业一点的角度来看这个问题。我们用一种叫做食物网的有向图来描述生物之间的关系:
一个食物网有N个点,代表N种生物,如果生物x可以吃生物y,那么从y向x连一个有向边。
这个图没有环。
图中有一些点没有连出边,这些点代表的生物都是生产者,可以通过光合作用来生存; 而有连出边的点代表的都是消费者,它们必须通过吃其他生物来生存。
如果某个消费者的所有食物都灭绝了,它会跟着灭绝。
我们定义一个生物在食物网中的“灾难值”为,如果它突然灭绝,那么会跟着一起灭绝的生物的种数。
举个例子:在一个草场上,生物之间的关系是:
如果小强和阿米巴把草原上所有的羊都给吓死了,那么狼会因为没有食物而灭绝,而小强和阿米巴可以通过吃牛、牛可以通过吃草来生存下去。所以,羊的灾难值是1。但是,如果草突然灭绝,那么整个草原上的5种生物都无法幸免,所以,草的灾难值是4。
给定一个食物网,你要求出每个生物的灾难值。
输入
输入文件 catas.in 的第一行是一个正整数 N,表示生物的种数。生物从 1 标号到 N。
接下来 N 行,每行描述了一个生物可以吃的其他生物的列表,格式为用空格隔开的若干个数字,每个数字表示一种生物的标号,最后一个数字是 0 表示列表的结束。
输出
包含N行,每行一个整数,表示每个生物的灾难值。
样例输入
5
0
1 0
1 0
2 3 0
2 0
样例输出
4
1
0
0
0
题解
拓扑排序+倍增LCA
暴力的n^2算法肯定是不行的,我们需要考虑特殊的做法。
由于每次有且只有一种生物灭绝,而当且仅当x的所有食物都灭绝,x才会灭绝。
所以所有x的食物从上至下能共同达到的某个节点灭绝时,x才会灭绝。
根据这个我们可以重构树然后跑LCA。
先把所有没有食物的点(题目中描述为“生产者”)向0连一条边。
然后对原图进行拓扑排序,得到每个点进队列的顺序。
再从后向前扫一遍,对于每个点,对它的所有食物节点求一下LCA(两两求一次),将这个点连到LCA上。
由于我们加了“生产者”->0的一条边,所以能够使得加进来的是一棵树,而不是一个森林。
每次重构时,不需要LCT,直接看作新加一个节点x,f[x][0]=LCA,然后处理一下就行了。
最后要求的就是子树大小-1,所以利用拓扑排序(也就是一种bfs序)向上更新即可。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; int n , head[70000] , to[1000000] , next[1000000] , cnt , rd[70000] , q[70000] , l = 1 , r , fa[70000][20] , deep[70000] , log[70000] , si[70000]; void add(int x , int y) { to[++cnt] = y; next[cnt] = head[x]; head[x] = cnt; } void topsort() { int i , j , x; for(i = 0 ; i <= n ; i ++ ) for(j = head[i] ; j ; j = next[j]) rd[to[j]] ++ ; for(i = 0 ; i <= n ; i ++ ) if(!rd[i]) q[++r] = i; while(l <= r) { x = q[l ++ ]; for(i = head[x] ; i ; i = next[i]) { rd[to[i]] -- ; if(!rd[to[i]]) q[++r] = to[i]; } } } int lca(int x , int y) { int i; if(deep[x] < deep[y]) swap(x , y); for(i = log[deep[x] - deep[y]] ; ~i ; i -- ) if(deep[x] - (1 << i) >= deep[y]) x = fa[x][i]; if(x == y) return x; for(i = log[deep[x]] ; ~i ; i -- ) if(fa[x][i] != fa[y][i]) x = fa[x][i] , y = fa[y][i]; return fa[x][0]; } int main() { int i , j , t , x; scanf("%d" , &n); for(i = 1 ; i <= n ; i ++ ) { scanf("%d" , &t); if(!t) add(i , 0); else { do { add(i , t); scanf("%d" , &t); }while(t); } } topsort(); for(i = 2 ; i <= n + 1 ; i ++ ) log[i] = log[i >> 1] + 1; for(i = n ; i ; i -- ) { x = q[i] , t = to[head[x]]; for(j = next[head[x]] ; j ; j = next[j]) t = lca(t , to[j]); fa[x][0] = t; deep[x] = deep[t] + 1; for(j = 1 ; j <= log[deep[x]] ; j ++ ) fa[x][j] = fa[fa[x][j - 1]][j - 1]; } for(i = 1 ; i <= n ; i ++ ) si[q[i]] ++ , si[fa[q[i]][0]] += si[q[i]]; for(i = 1 ; i <= n ; i ++ ) printf("%d\n" , si[i] - 1); return 0; }