【洛谷P2597】灾难
题目
题目链接:https://www.luogu.com.cn/problem/P2597
一个食物网有 \(n\) 个点,代表 \(n\) 种生物,生物从 \(1\) 到 \(n\) 编号。
如果生物 \(x\) 可以吃生物 \(y\),那么从 \(y\) 向 \(x\) 连一个有向边。
这个图没有环。
图中有一些点没有连出边,这些点代表的生物都是生产者,可以通过光合作用来生存。
而有连出边的点代表的都是消费者,它们必须通过吃其他生物来生存。
如果某个消费者的所有食物都灭绝了,它会跟着灭绝。
我们定义一个生物在食物网中的“灾难值”为,如果它突然灭绝,那么会跟着一起灭绝的生物的种数。
给定一个食物网,你要求出每个生物的灾难值。
\(n\leq 65534,m\leq 10^6\)。
思路
如果 \(x\) 吃 \(y\),那么就从 \(y\) 向 \(x\) 连边。这样我们得到了一张 DAG。每一个点的答案就是直接或间接连向他的点的数量。
建立一个虚根 \(S\),从 \(S\) 向度数为 \(0\) 的点连边。然后拓扑排序。每次取出队首 \(u\) 后,用倍增 LCA 求它的祖先,然后枚举 \(u\) 连向的点 \(v\),容易发现 \(v\) 应该被它被连向的点的 LCA 支配。然后不断更新 \(v\) 的父亲节点即可。
最后建出支配树,答案就是每一个点的子树大小了。
时间复杂度 \(O(n\log n)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=70010,M=1000010,LG=18;
int n,S;
struct edge
{
int next,to;
};
struct Tree
{
int tot,head[N],dep[N],f[N][LG+1],siz[N];
edge e[N];
void add(int from,int to)
{
e[++tot]=(edge){head[from],to};
head[from]=tot;
}
int lca(int x,int y)
{
if (dep[x]<dep[y]) swap(x,y);
for (int i=LG;i>=0;i--)
if (dep[f[x][i]]>=dep[y]) x=f[x][i];
if (x==y) return x;
for (int i=LG;i>=0;i--)
if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
return f[x][0];
}
void dfs(int x)
{
siz[x]=1;
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
if (v!=f[x][0])
{
dfs(v);
siz[x]+=siz[v];
}
}
}
}T;
struct Graph
{
int tot,head[N],deg[N];
edge e[M+N];
void add(int from,int to)
{
e[++tot]=(edge){head[from],to};
head[from]=tot; deg[to]++;
}
void topsort()
{
memset(T.f,-1,sizeof(T.f));
queue<int> q;
for (int i=1;i<=n;i++)
if (!deg[i]) add(S,i);
q.push(S); T.f[S][0]=0;
while (q.size())
{
int u=q.front(); q.pop();
T.dep[u]=T.dep[T.f[u][0]]+1;
for (int i=1;i<=LG;i++)
T.f[u][i]=T.f[T.f[u][i-1]][i-1];
for (int i=head[u];~i;i=e[i].next)
{
int v=e[i].to;
if (T.f[v][0]==-1) T.f[v][0]=u;
else T.f[v][0]=T.lca(u,T.f[v][0]);
deg[v]--;
if (!deg[v]) q.push(v);
}
}
}
}G;
int main()
{
memset(G.head,-1,sizeof(G.head));
memset(T.head,-1,sizeof(T.head));
scanf("%d",&n);
for (int i=1,x;i<=n;i++)
while (scanf("%d",&x) && x) G.add(x,i);
S=n+1;
G.topsort();
for (int i=1;i<=n;i++)
T.add(T.f[i][0],i);
T.dfs(S);
for (int i=1;i<=n;i++)
printf("%d\n",T.siz[i]-1);
return 0;
}