洛谷 P2597 [ZJOI2012]灾难(拓扑排序+建树+动态LCA+树上前缀和)

6 篇文章 0 订阅
6 篇文章 0 订阅

前言(传送门

这是一道神奇的生物题,我和同桌讨论了一节课,然后想到了一些和正解接近的东西,但还是没想到具体的做法。在看完题解后,感觉这题并不难,但是有点神奇。


题解

首先,食物网是个DAG,我们将食物连向捕食者,并建立虚点表示太阳,并连向所有的生产者。此时基图连通。

拓扑排序,我们想知道生物x的灾难值,但我们是从食物开始做的,所以考虑哪些生物死了会导致x死掉。一种生物的死亡必然是其所有食物死亡导致的,而食物也要吃其他生物。于是这是一个依赖结构。

我们考虑对原图建树,树上的每个节点死掉儿子及后代都会死。那x该连向谁呢?明显是其所有食物的LCA,x死(被一种生物影响)就是LCA或其祖先死,等价于LCA死。

充分性:x所有食物的LCA一死,食物全死,x就没饭吃了,死。
必要性:若LCA活着,则必然有食物没死,x还能吃,所以LCA必死。

我们Topo时食物已经从树上向下连好边了,于是直接动态求所有食物的LCA即可。由于插入x只改变x的信息,直接计算其dep和倍增数组就行了。然后由LCA向x连边。

最后连出一棵“灭绝树”,每个点灭绝,子树全部灭绝。于是每种生物的灾难值就是其子树大小-1,一次DFS就出来了。(洛谷题解中所谓的求树上前缀和就是计算这个)

由于读入不超过1M,所以跑得过。


代码

#include <cstdio>
#include <cstring>
#include <iostream>
#include <cstdlib>
#include <algorithm>
#include <cmath>
#include <vector>
#include <queue>
#define maxn 66666
#define Lg 20

using namespace std;

int n, cur = -1;

struct List{
    int obj;
    List *next;
}*head[maxn], Edg[maxn];

vector <int> P[maxn], food[maxn];
queue <int> Q;

void Addedge(int a, int b){
    Edg[++cur].next = head[a];
    Edg[cur].obj = b;
    head[a] = Edg+cur;
}

int in[maxn], f[Lg][maxn], dep[maxn], sum[maxn];

int LCA(int x, int y){
    if(dep[x] > dep[y])  swap(x, y);

    for(int i = 18; i >= 0; i--)
        if(dep[f[i][y]] >= dep[x])
            y = f[i][y];

    if(x == y)  return x;

    for(int i = 18; i >= 0; i--)
        if(f[i][x] != f[i][y]){
            x = f[i][x];
            y = f[i][y];
        }

    return f[0][x];
}

void Topo(){

    for(int i = 1; i <= n; i++){
        if(!in[i]){  
            in[i] = 1;
            P[0].push_back(i);
            food[i].push_back(0);
        }
    }

    Q.push(0);
    dep[0] = 1;

    for(int i = 0; i <= 18; i++)
        f[i][0] = 0;

    for(int i = 0; i <= n; i++)  head[i] = NULL;

    while(!Q.empty()){
        int now = Q.front();
        Q.pop();

        int siz1 = P[now].size();

        for(int i = 0; i < siz1; i++){
            int v = P[now][i];
            in[v] --;
            if(!in[v]){  
                Q.push(v);

                int lca = now;

                int siz2 = food[v].size();

                for(int j = 0; j < siz2; j++)
                    lca = LCA(lca, food[v][j]);

                Addedge(lca, v);

                f[0][v] = lca;

                dep[v] = dep[lca] + 1;

                for(int j = 1; j <= 18; j++)
                    f[j][v] = f[j-1][f[j-1][v]];
            }
        }
    }

}

void Dfs(int x){

    sum[x] = 1;

    for(List *p = head[x]; p; p = p->next){
        int v = p->obj;
        Dfs(v);
        sum[x] += sum[v];
    }
}

int main(){

    scanf("%d", &n);

    int x;
    for(int i = 1; i <= n; i++){
        while(~ scanf("%d", &x) && x){
            P[x].push_back(i);
            food[i].push_back(x);
            in[i] ++;
        }
    }

    Topo();

    Dfs(0);

    for(int i = 1; i <= n; i++)
        printf("%d\n", sum[i] - 1);

    return 0;
} 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值