P2597 [ZJOI2012]灾难(topsort+LCA)

题目链接

题意:给一张图代表食物链,图中没有环,,如果x捕食y,那么有一条有向边,由x指向y,如果某个物种的捕食对象全灭绝了,那么该物种就会灭绝,定义了一个名词“灾难值”,每个物种的灾难值为它灭绝后,会导致多少物种灭绝。输出所有1-n物种的灾难值。

思路:如果一个物种有多个捕食对象,那么该物种的所有捕食对象都灭绝了才会使得该物种灭绝,可以知道一个物种捕食对象都灭绝的条件为它所有捕食对象的最近公共祖先灭绝以及能够使得最近公共祖先灭绝的其他物种灭绝,于是可以根据这个条件重新建图,即为将一个物种挂到它所有捕食对象的LCA的下面,该步需要保证遍历到该物种的时候,它所有的子节点已经处理过了,所以需要用topsort处理。重现建图后,某个点的答案即为该点的子树大小减1,第一张给出的图需要反向建边,因为需要先处理被捕食的物种。

代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
struct node{
    int x,to;
};
node e[200010],ee[200010];
int idx=0,idxx=0,h[100010],hh[100010],depth[100010];
int d[100010],n,st[100010],fa[100010],ans[100010],anc[100010][21];
void add(int a,int b,int id){
    if(id==0){
        e[idx].x=b,e[idx].to=h[a],h[a]=idx++;
    }
    else{
        ee[idxx].x=b,ee[idxx].to=hh[a],hh[a]=idxx++;
    }
}
int LCA(int a,int b){
    if(depth[a]<depth[b])swap(a,b);
    for(int k=18;k>=0;k--){
        if(depth[anc[a][k]]>=depth[b]){
            a=anc[a][k];
        }
    }
    if(a==b)return a;//其中一个为另一个的祖先
    for(int k=18;k>=0;k--){
        if(anc[a][k]!=anc[b][k]){
            a=anc[a][k];
            b=anc[b][k];
        }
    }
    return anc[a][0];//向上第一个即为祖先
}
void dfs(int x){
    ans[x]=1;
    for(int i=hh[x];i!=-1;i=ee[i].to){
        int j=ee[i].x;
        dfs(j);
        ans[x]+=ans[j];
    }
}
int main(){
    memset(h,-1,sizeof h);
    memset(hh,-1,sizeof hh);
    memset(fa,-1,sizeof fa);
    scanf("%d",&n);  
    int x;
    for(int i=1;i<=n;i++){
        while(1){
            scanf("%d",&x);
            if(x==0)break;
            add(x,i,0);
            d[i]++;
        }
    }
    int l=1,r=0;
    for(int i=1;i<=n;i++){
        if(d[i]==0){
            st[++r]=i;
            fa[i]=0;
        }
    }
    while(l<=r){
        x=st[l++];
        add(fa[x],x,1);
        anc[x][0]=fa[x],depth[x]=depth[fa[x]]+1;        
        for(int i=1;i<=18;i++) anc[x][i]=anc[anc[x][i-1]][i-1];//更新倍增数组 
        for(int i=h[x];i!=-1;i=e[i].to){
            int j=e[i].x;
            if(fa[j]==-1)fa[j]=x;
            else fa[j]=LCA(fa[j],x);
            d[j]--;
            if(!d[j]){
                st[++r]=j;
            }
        }
    }
    dfs(0);
    for(int i=1;i<=n;i++){
        printf("%d\n",ans[i]-1);
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值