题目描述
阿米巴是小强的好朋友。
阿米巴和小强在草原上捉蚂蚱。小强突然想,如果蚂蚱被他们捉灭绝了,那么吃蚂蚱的小鸟就会饿死,而捕食小鸟的猛禽也会跟着灭绝,从而引发一系列的生态灾难。
学过生物的阿米巴告诉小强,草原是一个极其稳定的生态系统。如果蚂蚱灭绝了,小鸟照样可以吃别的虫子,所以一个物种的灭绝并不一定会引发重大的灾难。
我们现在从专业一点的角度来看这个问题。我们用一种叫做食物网的有向图来描述生物之间的关系:
一个食物网有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;
}
京公网安备 11010502036488号