【BzoJ 3319】【黑白树】【并查集】【”猥琐欲为“】

这里写图片描述
最近迷上了一个叫splatoon的游戏呢


最近做了几道并查集的题,完全被虐成狗了。。。才明白如果并查集考灵活会这么难啊。。。


3319: 黑白树

Time Limit: 10 Sec Memory Limit: 512 MB

Description

给定一棵树,边的颜色为黑或白,初始时全部为白色。维护两个操作:

1.查询u到根路径上的第一条黑色边的标号。
2.将u到v 路径上的所有边的颜色设为黑色。

Notice:这棵树的根节点为1

Input

第一行两个数n,m分别表示点数和操作数。
接下来n-? 1行,每行2个数u,v.表示一条u到v的边。
接下来m行,每行为以下格式:
1 v 表示第一个操作
2 v u 表示第二种操作

Output

对于每个询问,输出相应答案。如果不存在,输出0。
Sample Input
5 4
1 2
1 3
2 4
2 5
1 2
2 2 3
1 3
1 4

Sample Output

0
2
1

HINT

对于 100% 的数据:n,m<=10^6

Source

[Submit][Status][Discuss]



这道题目选用并查集去解决其实是一个非常巧妙的思路,最重要的是并查集的几个很有趣的性质:
【1】一个元素集合的所有元素的答案一样
【2】修改一个元素集合的值只用修改其带头元素(parent)的值即可

题解:

先用并查集将所有最终为黑边的边的端点合并 , 并记录下该条边最早变为黑边的时间 ; 然后反着来 , 将最终仍未被黑边连接的点用并查集合并起来 ,(此处一个集合的元素满足其中的所有点都是被白边所连接), 求出该集合的答案(如【1】所说 , 同一集合的元素的答案相同),然后反着来,不断更新白点,合并点集(有些黑边会被消去),维护当前的答案(利用【2】);


代码如下:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define M 2100001
using namespace std;
int n , m , cnt;
int inv[M] , inv_x[M] , inv_y[M] , inv_num[M];
int ind[M] , nex[M] , e[M] , pot[M] , tot;
int q_typ[M] , q_x[M] , q_y[M];
int f[M] , fa[M];
int deep[M];
int ans[M];
int pb[M];

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

void add(int a , int b , int c){
    nex[++tot] = ind[a];
    e[ind[a] = tot] = b;
    inv_num[tot] = c;
}

void dfs(int v , int u){
    fa[v] = u;
    deep[v] = deep[u] + 1;
    for(int i = ind[v] ; i ; i = nex[i]){
        if(e[i] == u)continue;
        inv[e[i]] = i;
        inv_x[i]  = v;
        inv_y[i]  = e[i];
        dfs(e[i] , v);
    }
}

void change(int x , int y , int opt){
    x = find(x) , y = find(y);
    while(x != y){
        if(deep[x] < deep[y])swap(x , y);
        if(!pot[x])pot[x] = opt , f[x] = f[fa[x]];
        x = f[fa[x]];
    }
}

void solve(int x , int y , int opt){
    x = find(x) , y = find(y);
    while(x != y){
        if(deep[x] < deep[y])swap(x , y);
        if(pot[x] == opt) f[x] = f[fa[x]];
        x = fa[x];
    }
}

int main(){
    scanf("%d%d",&n,&m);
    for(int i = 1 ; i < n ; ++i){
        int u , v;
        scanf("%d%d",&u,&v);
        add(u , v , i);
        add(v , u , i);
    }
    dfs(1 , 0);
    for(int i = 1 ; i <= n ; ++i)f[i] = i;
    for(int i = 1 ; i <= m ; ++i){
        scanf("%d%d",&q_typ[i],&q_x[i]);
        if(q_typ[i] == 2){
            scanf("%d",&q_y[i]);
            change(q_x[i] , q_y[i] , i);
        }
    }
    for(int i = 1 ; i <= n ; ++i)f[i] = i;
    for(int i = 2 ; i <= n ; ++i){
        if(!pot[i]){
            int x = find(inv_x[inv[i]]) , y = find(inv_y[inv[i]]);
            if(x == y)continue;
            if(deep[x] > deep[y])swap(x , y);
            f[y] = x;
        }
    }
    for(int i = m ; i >= 1 ; --i){
        if(q_typ[i] == 1)ans[++cnt] = inv_num[inv[find(q_x[i])]];
        else solve(q_x[i] , q_y[i] , i);
    }
    for(int i = cnt ; i ; --i)printf("%d\n",ans[i]);
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值