[zjoi2012]灾难——拓扑排序+灭绝树

题目大意:

草原中有一张食物网,如果将一种动物弄死,将会有一些动物没有食物而死亡,那么称死亡动物的种类数为这种动物的灾难值。
求每一种动物的灾难值。
\(n\leq 65534\),输入数据\(\leq\) 10M。

思路:

题目可以转化为将DAG删除一个结点后有多少个结点变得不可达
不难发现,整张食物网其实是构成了一个树形结构,即直接导致某种动物死亡的动物有且只有一种。某一个结点的死亡必定会导致它的子树内的所有的动物都死亡。
于是我们尝试将这颗“灭绝树”给建出来,一种动物v要死亡,那么直接连向它的所有结点\(u_1,u_2,u_3\dots\)都必须要死亡。那么我们只要找到第一个可以使{u}全部死亡的结点,它就是v的父亲。
{u}必须在v之前处理出来,并且加入灭绝树中,于是直接按照拓扑序建树。之后的找父亲就是一个求lca的过程,只需要每次更新新结点的st表即可。

#include<bits/stdc++.h>

#define REP(i,a,b) for(int i=a,i##_end_=b;i<=i##_end_;++i)
#define debug(x) cout<<#x<<"="<<x<<endl
typedef long long ll;

using namespace std;

void File(){
    freopen("bzoj2815.in","r",stdin);
    freopen("bzoj2815.out","w",stdout);
}

template<typename T>void read(T &_){
    T __=0,mul=1; char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-')mul=-1;
        ch=getchar();
    }
    while(isdigit(ch))__=(__<<1)+(__<<3)+(ch^'0'),ch=getchar();
    _=__*mul;
}

const int maxn=65534+10;
const int maxm=2e6+10;
int n,rt,deg[maxn],st[maxn][21],dep[maxn],Log[maxn],sz[maxn];
int beg[maxn],to[maxm<<1],las[maxm<<1],cnte=1;
vector<int>T[maxn],from[maxn]; 

void add(int u,int v){
    las[++cnte]=beg[u]; beg[u]=cnte; to[cnte]=v;
}

void init(){
    read(n);
    rt=n+1;
    Log[1]=0;
    REP(i,2,n)Log[i]=Log[i/2]+1;
    int v;
    REP(i,1,n){
        read(v);
        while(v)add(v,i),++deg[i],read(v);
    }
    REP(i,1,n)if(!deg[i])add(rt,i),++deg[i];
}

void update(int u){
    REP(i,1,18){
        int f=st[u][i-1];
        if(!f || !st[f][i-1])break;
        st[u][i]=st[f][i-1];
    }
}

int query(int x,int y){
    if(dep[x]<dep[y])swap(x,y);
    while(dep[x]!=dep[y]){
        int d=Log[dep[x]-dep[y]];
        x=st[x][d];
    }
    if(x==y)return x;
    for(int d=18;d>=0;--d)
        if(st[x][d] && st[y][d] && st[x][d]!=st[y][d])
            x=st[x][d],y=st[y][d];
    return st[x][0];
}

void Topsort(){
    queue<int>qu;
    qu.push(rt);
    while(!qu.empty()){
        int u=qu.front(); qu.pop();
        if(u!=rt){
            int t=from[u][0];
            REP(i,1,from[u].size()-1)
                t=query(t,from[u][i]);
            st[u][0]=t; update(u);
            dep[u]=dep[st[u][0]]+1;
        }
        for(int i=beg[u];i;i=las[i]){
            int v=to[i];
            --deg[v]; from[v].push_back(u);
            if(!deg[v])qu.push(v);
        }
    }
}

void dfs(int u){
    ++sz[u];
    REP(i,0,T[u].size()-1){
        dfs(T[u][i]);
        sz[u]+=sz[T[u][i]];
    }
}

int main(){
    File();
    init();
    Topsort();
    REP(i,1,n)T[st[i][0]].push_back(i);
    dfs(rt);
    REP(i,1,n)printf("%d\n",sz[i]-1);
    return 0;
}

转载于:https://www.cnblogs.com/ylsoi/p/9861135.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值