题目大意
给定一张食物网,对于每一种生物,求其灾难值。灾难值的定义为
如果它突然灭绝,那么会跟着一起灭绝的生物的种数。
Solution
很多 d a l a o dalao dalao说是支配树。
呃?不知道支配树??
没事,我也不知道,但是这题看完之后自然就知道了。
如果给你的不是一个 D A G DAG DAG,而是一棵树,那么怎么做?
这不简单?每个节点的答案就是子树大小 − 1 -1 −1,因为要减去自己。
那我们就把图变成一棵树。
我们以样例为例。
首先草是一切生物的食物来源,所以其等级最高,就会被建在我们新建的树的根节点。
所以我们是怎么找到草的呢?不知道。但是如果反向建边呢?
很明显,就是入度为 0 0 0的点。
然后考虑下一个节点,比如说考虑节点 2 2 2,显然,在重构的树中,节点 2 2 2应该是 1 1 1的儿子。这是一个前驱的情况,那么比如说节点 4 4 4这样有 2 , 3 2,3 2,3两个前驱的呢?显然不可能是 2 , 3 2,3 2,3的儿子,根据建树的目的,可以想到, 4 4 4应该是 1 1 1的儿子,因为只有 1 1 1灭绝了,才有可能使 4 4 4灭绝,而 1 1 1是怎么找到的呢?在 4 4 4之前,显然 2 , 3 2,3 2,3已经作为 1 1 1的儿子建在树上了,而 1 1 1恰好就是 4 4 4的所有前驱的 L C A LCA LCA。
所以建树时,对于每个节点 u u u,要得出 u u u的所有前驱,然后求出这些前驱的在重构的树中的 L C A LCA LCA,然后将 u u u建为 L C A LCA LCA的儿子。其合理性非常简单,在重构的树中,要求一个父节点灭绝之后,其所有的子节点都灭绝,为了使 u u u灭绝,必须使所有 u u u的前驱灭绝,为了使 u u u的所有前驱灭绝,只有让这些点在重构树上的 L C A LCA LCA灭绝,所以当 L C A LCA LCA灭绝了, u u u才会灭绝,所以 u u u要作为 L C A LCA LCA的直接儿子。
最后一点就是,为了使在处理 u u u时,所有的 u u u的前驱都被处理过了,就要用到 t o p o topo topo。
而最后建出来的这棵树,就被称为支配树,上述就是对于 D A G DAG DAG的建支配树的方法。
你,学废了吗?
为了写代码方便,避免出现多个初始入度为 0 0 0的点,可以建立一个大起点(太阳?),从而转化为一个起点的情况。
Code
#include<bits/stdc++.h>
#define ll long long
#define inf 1<<30
using namespace std;
const int MAXN=1e6+10;
vector<int> e[MAXN],tr[MAXN],lk[MAXN];
//e存原图 tr存树图 lk存点的前驱
int in[MAXN],f[MAXN][20],dep[MAXN];
int LCA(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
int dlt=dep[x]-dep[y];
for(int i=0;i<20;i++)
if(dlt&(1<<i))
x=f[x][i];
if(x==y) return x;
for(int i=19;i>=0;i--)
if(f[x][i]!=f[y][i])
x=f[x][i],y=f[y][i];
return f[x][0];
}//倍增求LCA
int siz[MAXN];
void dfs(int x,int fa){
siz[x]=1;
for(int i=0;i<tr[x].size();i++){
int s=tr[x][i];
if(s==fa) continue;
dfs(s,x);
siz[x]+=siz[s];
}
}
int main()
{
int n,z;
scanf("%d",&n);
for(int i=1;i<=n;i++){
while(~scanf("%d",&z)&&z){
e[z].push_back(i);//注意反向建边,别被题目误导
in[i]++;
}
}
int rot=0;
dep[rot]=0;//大起点
queue<int> q;
for(int i=1;i<=n;i++)
if(!in[i]){
lk[i].push_back(rot);//将大起点放入i的前驱中
q.push(i);
}
while(!q.empty()){
int x=q.front();q.pop();
int lca=lk[x][0];
for(int i=1;i<lk[x].size();i++)
lca=LCA(lca,lk[x][i]);//求出树中的lca
dep[x]=dep[lca]+1;
f[x][0]=lca;
for(int i=1;i<20;i++)
f[x][i]=f[f[x][i-1]][i-1];//倍增的预处理
tr[lca].push_back(x);//将x建入lca的儿子
for(int i=0;i<e[x].size();i++){
int s=e[x][i];
lk[s].push_back(x);//x是s的前驱之一
if(--in[s]==0) q.push(s);
}
}
dfs(rot,-1);//跑出每个点的子树大小
for(int i=1;i<=n;i++)
printf("%d\n",siz[i]-1);//然后减一就是答案。
}