P2597 [ZJOI2012]灾难(有向无环图的支配树)

该文章探讨了如何利用有向无环图(DAG)的反图和拓扑排序解决一类灾难问题。通过构建虚拟源点连接所有入度为0的节点,然后进行拓扑排序,并结合最近公共祖先(LCA)建立支配树。在支配树中,每个节点支配的点数等于其子树大小减去自身。
摘要由CSDN通过智能技术生成

题意:给出有向无环图,若存在一条u->v的有向边,代表u吃v,现在问你去掉一种食物,会有多少动物灭绝。

考虑建反图,就变成了食物支配动物,再建一个虚拟源点,连接所有的入度为0的点,以此开始跑拓扑排序,然后用拓扑序和lca建支配树。
那支配树上的某点能支配的点就是他的子树大小,减去他本身。

#include<bits/stdc++.h>
using namespace std;

const int maxn=1e5+7;
const int maxm=4e5+7;

struct Edge{
    int v,next;
}edge[maxm<<1];

//正向图,反向图,支配树;
int head[maxn],headfan[maxn],headzhipei[maxn],top;

int fa[maxn][19];
int xxx;//防越界2333,内存连续,dep[-1]=xxx,这是个小技巧...?
int dep[maxn];
int mi=18;

int du[maxn];
void init(int n){
    memset(head,-1,sizeof(head));
    memset(headfan,-1,sizeof(headfan));
    memset(headzhipei,-1,sizeof(headzhipei));
    top=0;
    dep[n+1]=1;
}

void add(int u,int v,int head[]){
    edge[top].v=v;
    edge[top].next=head[u];
    head[u]=top++;
}
queue<int> q;
int num;
int t[maxn];//拓扑序;
int n;
void topsort(int st){
    num=0;
    while(!q.empty()) q.pop();
    for(int i=1;i<=n;++i)
        if(du[i]==0)
            add(st,i,head),add(i,st,headfan),q.push(i);
    int u,v;
    while(!q.empty()){
        u=q.front(); q.pop();
        t[++num]=u;
        for(int i=head[u];i!=-1;i=edge[i].next){
            v=edge[i].v;
            --du[v];
            if(du[v]==0) q.push(v);
        }
    }
}

int LCA(int x,int y){
    if(x==-1||y==-1) return -1;
    if(dep[x]<dep[y]) swap(x,y);
    for(int i=mi;i>=0;--i)
        if(dep[fa[x][i]]>=dep[y]) x=fa[x][i];
    if(x==y) return x;
    for(int i=mi;i>=0;--i)
        if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
    return fa[x][0];
}

int Size[maxn];

//求支配树上点的子树大小;
void dfs(int u){
    Size[u]=1;
    int v;
    for(int i=headzhipei[u];i!=-1;i=edge[i].next){
        v=edge[i].v;
        if(v==-1) continue;
        dfs(v);
        Size[u]+=Size[v];
    }
}

int main(){
    scanf("%d",&n);
    init(n);
    int u,v;
    for(int i=1;i<=n;++i){
        while(1){
            scanf("%d",&v);
            if(v==0) break;
            //i吃v,v支配i;
            add(v,i,head);
            add(i,v,headfan);
            ++du[i];
        }
    }
    topsort(n+1);
    int lca;
    for(int i=1;i<=n;++i){
        u=t[i];
        lca=-1;
        if(headfan[u]!=-1) lca=edge[headfan[u]].v;
        for(int j=headfan[u];j!=-1;j=edge[j].next){
            v=edge[j].v;
            lca=LCA(lca,v);
        }
        dep[u]=dep[lca]+1;
        if(lca!=-1) add(lca,u,headzhipei);
        fa[u][0]=lca;
        for(int j=1;j<=mi;++j) fa[u][j]=fa[fa[u][j-1]][j-1];
    }
    dfs(n+1);
    for(int i=1;i<=n;++i) printf("%d\n",Size[i]-1);

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值