bzoj2815: [ZJOI2012]灾难

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

题面在这里

题意:

有一个n个点的关系图,u->v有边表示u能吃v。
去掉某个点以后会有一些点没有东西吃,每个点的灾难值定义为如果去掉这个点,会没有东西吃的点的个数。
求每个点的灾难值。
n<=65534

做法:

好妙啊QAQ..

首先这个不是树很难受,如果是一棵树,就可以直接计算子树大小得到答案了。
我们考虑把图化成一棵树。
观察到一个奇妙的性质,一个点u,如果和很多点v1,v2…vn都有边(代表u能吃v1,v2…vn),那么它能吃的东西相当于是v1,v2…vn这些点的共同能吃的东西。
于是你想到了什么!结合一下“树”的知识。

lca啊(雾)!

假如我们反着建边,从上往下考虑,如果发现一个点的父亲有多个,就把这个点连向它所有父亲的lca。一层层往下建就建出了一棵树。
于是在树上dfs一遍就得到答案了。

代码:

/*************************************************************
    Problem: bzoj 2815 [ZJOI2012]灾难
    User: fengyuan
    Language: C++
    Result: Accepted
    Time: 212 ms
    Memory: 17452 kb
    Submit_Time: 2018-01-24 19:34:32
*************************************************************/

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cctype>
#include<cstdlib>
#include<cmath>
#include<queue>
using namespace std;
typedef long long ll;

inline ll read() {
    char ch = getchar(); ll x = 0; int op = 1;
    for(; !isdigit(ch); ch = getchar()) if(ch == '-') op = -1;
    for(; isdigit(ch); ch = getchar()) x = x*10+ch-'0';
    return x*op;
}
inline void write(ll a) {
    if(a < 0) putchar('-'), a = -a;
    if(a >= 10) write(a/10); putchar('0'+a%10);
}

const int N = 100000, M = 200010;
int n, cnt, tot;
int head[N], headt[N], f[N][25], depth[N], in[N], q2[N], sz[N], vis[N];
struct edge {
    int to, nxt;
    edge() {}
    edge(int x, int y) { to = x, nxt = y; }
}e[M], t[M];

inline void addedge(int x, int y) { e[++ cnt] = edge(y, head[x]); head[x] = cnt; }
inline void addtree(int x, int y) { t[++ cnt] = edge(y, headt[x]); headt[x] = cnt; }
inline void tp() {
    queue<int> q;
    for(int i = 1; i <= n; i ++) if(!in[i]) q.push(i);
    while(!q.empty()) {
        int u = q.front(); q.pop(); q2[++ tot] = u;
        for(int i = head[u]; i; i = e[i].nxt) {
            int v = e[i].to; in[v] --;
            if(!in[v]) q.push(v);
        }
    }
}
inline int lca(int x, int y) {
    if(x == -1) return y;
    if(depth[x] < depth[y]) swap(x, y);
    int tmp = depth[x] - depth[y];
    for(int i = 20; i >= 0; i --) if(tmp>>i&1) x = f[x][i];
    if(x == y) return x;
    for(int i = 20; i >= 0; i --)
        if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
    return f[x][0];
}
inline void build() {
    for(int i = tot; i >= 1; i --) {
        int u = q2[i], fa = -1;
        for(int j = head[u]; j; j = e[j].nxt) fa = lca(fa, e[j].to);
        if(fa == -1) fa = 0; addtree(fa, u); vis[u] = 1;
        depth[u] = depth[fa] + 1;
        f[u][0] = fa;
        for(int i = 1; 1<<i <= depth[u]; i ++) f[u][i] = f[f[u][i-1]][i-1];
    }
}
inline void dfs(int u) {
    sz[u] = 1;
    for(int i = headt[u]; i; i = t[i].nxt) {
        int v = t[i].to; dfs(v);
        sz[u] += sz[v];
    }
}
int main() {
    n = read();
    for(int i = 1; i <= n; i ++) {
        int x = read();
        while(x) { addedge(i, x); in[x] ++; x = read(); }
    }
    tp(); build(); dfs(0);
    for(int i = 1; i <= n; i ++) write(sz[i]-1), puts("");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值