COGS 2043 猴子 并查集

这应该是昨天某神犇新传上到OJ上的题,看到分类标签是并查集,就欣欣然地去做了一下。

说在前面:我做的时候题目中并没有说明数据范围,导致cheat一般地试出来了数据范围: n <= 20w, m <= 40w。

乍一看好像很复杂的样子,不过想明白了思路还是很简洁的。虽说是某只猴子用手抓另一只猴子,但是效果是一样的,不管是A抓着B还是B抓着A,只要其中有一个或直接或间接抓着1或者被1抓着都不会掉落,所以这个“抓着”是可以看作无向边的。这也是使用并查集的前提。

删边的操作很难,而添加边的操作是很容易的,本身构造并查集的树的过程也就是添边的过程。所以我们把猴子松手的过程倒过来,变成从第m次删的边到第一次删的边不断的添边,而最初构建并查集的过程不用这m条边。
就是说,先用n*2-m条边建出最开始的并查集,这时候与1相连的就是最终答案为-1的不会掉落的猴子。其他猴子掉落时间均设为m-1,之后从m到1不断把边加入,每加入一次,都合并一次并查集,如果有集合被合并到1所在的数上,那么这个集合的所有猴子的掉落时间都需要更新为i-1。

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#define M 200005
using namespace std;

int n, m, r[M][3], des[M<<1][3], f[M], fail[M];
bool unexi[M][3];

vector <int> bel[M];

int find(int x){
    return f[x] = f[x]==x ? x : find(f[x]);
}

int main()
{
    freopen("monkeya.in","r",stdin);
    freopen("monkeya.out","w",stdout);
    memset(fail, -1, sizeof fail);
    scanf("%d %d", &n, &m);
    for(int i = 1; i <= n; i++){
        f[i] = i;
        scanf("%d %d", r[i]+1, r[i]+2);
    }
    for(int i = 1; i <= m; i++){
        int u, v;
        scanf("%d %d", &u, &v);
        des[i][v] = u;
        unexi[u][v] = 1;
    }
    for(int i = 1; i <= n; i++){
        int fi = find(i);
        for(int k = 1; k < 3; k++){
            if(r[i][k] > 0 && !unexi[i][k]){
            int fv = find(r[i][k]);
            if(fi != fv) f[fv] = fi;
            }
        }
    }
    int f1 = find(1);
    for(int i = 1; i <= n; i++){
        int fi = find(i);
        bel[fi].push_back(i); 
        if(fi != f1) fail[i] = m-1;
    }

    for(int i = m; i; i--){
        int u, v, fu, fv;
        if(des[i][1]){
            u = des[i][1];
            v = r[u][1];
        } 
        else{
            u = des[i][2];
            v = r[u][2];
        }
        fu = find(u);
        fv = find(v);
        f1 = find(1);
        if(fu == fv) continue;
        int su = bel[fu].size();
        int sv = bel[fv].size();
        if(fu == f1){
            for(int j = 0; j < sv; j++){
                fail[bel[fv][j]] = i-1;
            }
        }
        if(fv == f1){
            for(int j = 0; j < su; j++){
                fail[bel[fu][j]] = i-1;
            }
        }
        if(su < sv){
            f[fu] = fv;
            for(int j = 0; j < su; j++){
                bel[fv].push_back(bel[fu][j]);
            }
            bel[fu].clear();
        }
        else{
            f[fv] = fu;
            for(int j = 0; j < sv; j++){
                bel[fu].push_back(bel[fv][j]);
            }
            bel[fv].clear();
        }
    }
    for(int i = 1; i <= n; i++){
        printf("%d\n", fail[i]);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值