题目描述
阿米巴是小强的好朋友。
阿米巴和小强在草原上捉蚂蚱。小强突然想,如果蚂蚱被他们捉灭绝了,那么吃蚂蚱的小鸟就会饿死,而捕食小鸟的猛禽也会跟着灭绝,从而引发一系列的生态灾难。
学过生物的阿米巴告诉小强,草原是一个极其稳定的生态系统。如果蚂蚱灭绝了,小鸟照样可以吃别的虫子,所以一个物种的灭绝并不一定会引发重大的灾难。
我们现在从专业一点的角度来看这个问题。我们用一种叫做食物网的有向图来描述生物之间的关系:
一个食物网有N个点,代表N种生物,如果生物x可以吃生物y,那么从y向x连一个有向边。
这个图没有环。
图中有一些点没有连出边,这些点代表的生物都是生产者,可以通过光合作用来生存; 而有连出边的点代表的都是消费者,它们必须通过吃其他生物来生存。
如果某个消费者的所有食物都灭绝了,它会跟着灭绝。
我们定义一个生物在食物网中的“灾难值”为,如果它突然灭绝,那么会跟着一起灭绝的生物的种数。
举个例子:在一个草场上,生物之间的关系是:
如果小强和阿米巴把草原上所有的羊都给吓死了,那么狼会因为没有食物而灭绝,而小强和阿米巴可以通过吃牛、牛可以通过吃草来生存下去。所以,羊的灾难值是1。但是,如果草突然灭绝,那么整个草原上的5种生物都无法幸免,所以,草的灾难值是4。
给定一个食物网,你要求出每个生物的灾难值。
题解:
第一反应是拓扑排序,这个没问题
入度为0的点灾难值绝对最大,而出度为0的点灾难值为0
但最难办的就是中间的点,如样例中的羊
羊没了,狼死了,但是小强没事
所以关键就是这种情况该怎么处理?
我们来分析一下,小强有两种食物,牛和羊,也就是只有当两者都没了,小强才会死,而牛和羊死了的条件就是草没了,可以这么理解,当草没了的时候,小强也会没。因为草是小强食物的lca
那么,我们就将草直接连接小强
改变后如图
你看这个图像什么?
是不是一个树,而对于每个点,子树的大小-1(减去本身)就是答案
总结一下:因为图中存在多点指一点,我们通过拓扑排序+lca生成新的树,而树的深度就是我们yaod
代码:
#include<bits/stdc++.h> #define N 65536 using namespace std; int n,tot,ans,tot1; int to[N*4],ne[N*4],head[N];//原树邻接表 int edge[N],anc[N][21],dad[N],size[N],de[N];// 入度、倍增、父亲、子树大小、深度 int to1[N*4],ne1[N*4],head1[N];//新树邻接表 void add(int x,int y) { to[++tot]=y,ne[tot]=head[x],head[x]=tot,edge[y]++; }//存原树 void add1(int x,int y) { to1[++tot1]=y,ne1[tot1]=head1[x],head1[x]=tot1;}//存新树 queue < int > q;//STL队列 void dfs(int x){//深搜求子树大小 size[x]=1; for(int i=head1[x];i;i=ne1[i]){ int y=to1[i]; dfs(y); size[x]+=size[y]; } } int lca(int x,int y){//求LCA if(x==y) return x; if(de[x]<de[y]) swap(x,y); for(int i=18;i>=0;i--) if(de[anc[x][i]]>=de[y]) x=anc[x][i]; if(x==y) return x; for(int i=18;i>=0;i--) if(anc[x][i]!=anc[y][i]) x=anc[x][i],y=anc[y][i]; return anc[x][0]; } void read(int &x) {//快读 int f = 1; x = 0; char ch = getchar(); while (ch < '0' || ch > '9') {if (ch == '-') f = -1; ch = getchar();} while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();} x *= f; } int main() { read(n); for(int i=1;i<N;i++) dad[i]=-1;//初始化 for(int i=1;i<=n;i++){ int x; read(x); while(x){ add(x,i);//反向存树 read(x); } } for(int i=1;i<=n;i++) if(!edge[i]){ q.push(i); dad[i]=0;//找到入度为0的边 } while(!q.empty()){ int x; x=q.front();q.pop();//取出队首 add1(dad[x],x); anc[x][0]=dad[x],de[x]=de[dad[x]]+1; for(int i=1;i<=18;i++) anc[x][i]=anc[anc[x][i-1]][i-1];//更新倍增数组 for(int i=head[x];i;i=ne[i]){ int y=to[i]; if(dad[y]==-1) dad[y]=x;//父亲之前没有被更新 else dad[y]=lca(dad[y],x);//父亲之前已经被更新过 if(--edge[y]==0) q.push(y);//入度为0则入队 } } dfs(0);//求子树大小 for(int i=1;i<=n;i++) printf("%d\n",size[i]-1);//输出 return 0; }