Problem
给出 n n n 种生物,这 n n n 种生物构成了一个食物网(如果生物 x x x 可以吃生物 y y y,那么从 y y y 向 x x x 连一个有向边。)
食物网中没有环。
图中有一些点没有连出边,这些点代表的生物都是生产者,可以通过光合作用来生存;而有连出边的点代表的都是消费者,它们必须通过吃其他生物来生存。
如果某个消费者的食物灭绝了,那么它也会跟着灭绝。
我们定义某种生物的灾难值为,如果它突然灭绝,那么会跟着一起灭绝的生物的种数。
给定一个食物网,你要求出每个生物的灾难值。
数据范围: 1 ≤ n ≤ 65534 1 ≤ n ≤ 65534 1≤n≤65534,输入文件的大小不超过 1M。
Solution
这是一道支配树的裸题啦。
我们按照生产者为根构建出支配树,那么显然,第 i i i 种生物的灾难值即为 i i i 子树的大小减 1 1 1。
于是我们建出支配树后在树上跑个 d f s dfs dfs 就可以了。
注意:由于生产者可能不止一个,我们先新建一个虚点向所有生产者连边。
Code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 200005
using namespace std;
int n,tot;
struct edge{
int t,first[N],v[N],nxt[N];
void add(int x,int y){
nxt[++t]=first[x],first[x]=t,v[t]=y;
}
}G,InvG,Semi,Idom;
int Set[N],Min[N],fa[N],in[N],dfn[N],pos[N],semi[N],idom[N],Size[N];
int find(int x){
if(Set[x]==x) return x;
int father=find(Set[x]);
if(dfn[semi[Min[Set[x]]]]<dfn[semi[Min[x]]]) Min[x]=Min[Set[x]];
return Set[x]=father;
}
void dfs(int x){
dfn[x]=++tot,pos[tot]=x;
for(int i=G.first[x];i;i=G.nxt[i]){
int to=G.v[i];
if(dfn[to]) continue;
fa[to]=x,dfs(to);
}
}
void Tarjan(){
for(int j=tot;j>1;--j){
int x=pos[j];
for(int i=InvG.first[x];i;i=InvG.nxt[i]){
int to=InvG.v[i];
if(!dfn[to]) continue;
find(to);
if(dfn[semi[Min[to]]]<dfn[semi[x]]) semi[x]=semi[Min[to]];
}
Semi.add(semi[x],x),Set[x]=fa[x];
int now=fa[x];
for(int i=Semi.first[now];i;i=Semi.nxt[i]){
int to=Semi.v[i];find(to);
if(semi[Min[to]]==now) idom[to]=now;
else idom[to]=Min[to];
}
}
for(int i=2;i<=tot;++i){
int x=pos[i];
if(idom[x]!=semi[x]) idom[x]=idom[idom[x]];
Idom.add(idom[x],x);
}
}
void DfsTree(int x){
Size[x]=1;
for(int i=Idom.first[x];i;i=Idom.nxt[i]){
int to=Idom.v[i];
DfsTree(to),Size[x]+=Size[to];
}
}
int main(){
int x,i;
scanf("%d",&n);
for(i=1;i<=n;++i)
Set[i]=Min[i]=semi[i]=i;
for(i=1;i<=n;++i){
scanf("%d",&x);
while(x!=0){
G.add(x,i),InvG.add(i,x),in[i]++;
scanf("%d",&x);
}
if(!in[i]) G.add(0,i),InvG.add(i,0);
}
dfs(0),Tarjan();
DfsTree(0);
for(i=1;i<=n;++i)
printf("%d\n",Size[i]-1);
return 0;
}