【BZOJ】2815: [ZJOI2012]灾难

题意简述:
一个食物网有 N 个点,代表 N 种生物,如果生物 x 可以吃生物 y,那么从 y 向 x 连一个有向边。
这个图没有环 。
图中有一些点没有连出边,这些点代表的生物都是生产者,可以通过光合作用来生存;
而有连出边的点代表的都是消费者,它们必须通过吃其他生物来生存。
如果某个消费者的所有食物都灭绝了,它会跟着灭绝。
我们定义一个生物在食物网中的“灾难值”为,如果它突然灭绝,那么会跟着一起灭绝的生物的种数。
给定一个食物网,你要求出每个生物的灾难值。

【输入格式】
输入文件的第一行是一个正整数 N,表示生物的种数。生物从 1 标号到 N。 接下来 N 行,每行描述了一个生物可以吃的其他生物的列表,格式为用空格隔开的若干个数字,每个数字表示一种生物的标号,最后一个数字是 0 表示列表的结束。

【输出格式】
输出文件包含 N 行,每行一个整数,表示每个生物的灾难值。

【样例输入】
5
0
1 0
1 0
2 3 0
2 0

【样例输出】
4
1
0
0
0

Solution
一个物种的灭绝条件为其食物全部灭绝。也就是说开始灭绝的物种要能有使某物种所有食物灭绝的能力,才能使这个物种灭绝。
考虑我们已经有一个树结构来表示,一个点灭绝会导致其子树中的物种全部灭绝。
那么使一个物种所有食物灭绝的灾难起点必然是所有食物LCA及其祖先。
所以我们可以把这个物种挂到该LCA下。
按照拓扑序做,动态加点和查LCA有很多方法,我写的倍增。
关于这棵树的正确性,仔细思考一下应该就能明白了。

#include<stdio.h>
#include<algorithm>
#define Void inline void 
#define cint const int &
#define N 70000
using namespace std;

int tot,Gtot,dep[N],f[N][18],n,Q[N],r,sz[N],s[N],Gs[N],len[N];

struct edge{int v,n;}e[N],Ge[N*10];
Void push(cint u,cint v){e[++tot]=(edge){v,s[u]};s[u]=tot;}
Void Gpush(cint u,cint v){Ge[++Gtot]=(edge){v,Gs[u]};Gs[u]=Gtot;}
Void build(cint u){dep[u]=dep[f[u][0]]+1;for (int i=1;f[u][i]=f[f[u][i-1]][i-1];i++);}
inline int LCA(int u,int v)
{
    if (dep[u]<dep[v]) swap(u,v);
    for (int i=16;0<=i;i--) if (dep[v]+(1<<i)<=dep[u]) u=f[u][i];
    if (u==v) return u;
    for (int i=16;0<=i;i--) if (f[u][i]!=f[v][i]) u=f[u][i],v=f[v][i];
    return f[u][0];
}

void dfs(cint u){sz[u]=1;for (int i=s[u];i;i=e[i].n) dfs(e[i].v),sz[u]+=sz[e[i].v];}

int main()
{
    scanf("%d",&n);
    for (int i=1,k=1;i<=n;i++,k=1)
    {
        while (k) scanf("%d",&k),Gpush(k,i),len[i]++;
        if (len[i]==1) Q[++r]=i,f[i][0]=n+1;
    }
    for (int i=1;i<=n;i++)
    {
        push(f[Q[i]][0],Q[i]);
        build(Q[i]);
        for (int j=Gs[Q[i]];j;j=Ge[j].n)
        {
            f[Ge[j].v][0]=f[Ge[j].v][0]?LCA(f[Ge[j].v][0],Q[i]):Q[i];
            if (--len[Ge[j].v]==1) Q[++r]=Ge[j].v;
        }
    }
    dfs(n+1);
    for (int i=1;i<=n;i++) printf("%d\n",sz[i]-1);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值