并查集

并查集

1.基础知识

1.1 边带权并查集

适用条件: 这样的方法适用于维护存在距离关系的情况,比如说统计相互间的大小关系,距离关系
具体方法: 设置一个d数组,d[u]表示u到fa[u]的距离(或者表示u和fa[u]的属性是否相同)
在两个地方需要维护:

  1. 在做get操作时维护a到pa之间的所有点的d值,这样求得的d值是这些点到pa的距离
// 查询+路径压缩+更新边权
int get(int x) {
   
    if (x == fa[x]) return x;
    int root = get(fa[x]);  // 找到根,并不断更新d数组
    d[x] += d[fa[x]];//这是维护距离的情况,如果维护属性那就d[x] ^= d[fa[x]]
    return fa[x] = root;
}
  1. 在合并的时候需要维护d[px] ( 或d[dy] ),这样求得的是px到root的距离
int px = get(x), py = get(y);
fa[px] = py;
d[px] = sz[y]  // 这里的公式需要推导,一般都是d'[x] - d[y] = t => (d[x] + d[px]) - d[y] = t, 然后推出d[px]的式子(x的父节点是px, px的父节点是root)

1.2 扩展域并查集

适用条件: 这样的方法适用于维护一个点只有少数几个属性的情况。同时使用扩展域并查集而不是2-sat的情况是,当前的一个条件可以推出它的4个命题(原命题、逆否命题、逆命题、否命题)。如果只能推出它的2个命题(原命题、逆否命题),那么使用2-sat。异或能够导出4个命题,与/或能导出2个命题。
具体方法: 扩展域就是把原来的点u替换为所有他的属性点,比如说u有3个属性a、b、c,那么就把u点替换为3个点ua,ub,uc。然后现在有i和j两个点,如果说i和j是相同的,那么说明 ia == ja, ib == jb ,ic == jc,即把ia和ja连一条边,ib和jb连一条边,ic和jc连一条边;如果说i吃j,那么把ia和jb连一条边,ib和jc连一条边,ic和ja连一条边。这里的连边表示等价关系。每次操作时,需要先检查所连的边是否是正确的,如果连边出现错误(比如是i和j是相同的,然而ia和jb相连,而相同隐含ia和ja相同,那么ja和jb相同,很明显,j的两个属性点不能相同;同时,检查的时候只需要检查u的一个属性点即可,因为u的所有属性点地位都是等价的),说明本次操作是错误的。强调:这里的连边都为merge操作,即把fa[py]=py,每次检查的本质是判断同一个物体的两个不同属性点是否相同,相同就是错误操作。

1.3 并查集判断奇环

这个方法是建立在:边带权并查集+并查集判断环的基础之上的,具体方法是维护一个d数组(和边带权并查集一样),然后按照并查集判断环的方法,如果px == py, 那么判断d[x] ^ d[y] == 1, 如果成立,那么出现奇环。

2. 例题

2.1 边带权并查集

acwing238. 银河英雄传说
**题意:**两个操作:
1、M i j,表示让第i号战舰所在列的全部战舰保持原有顺序,接在第j号战舰所在列的尾部。
2、C i j,表示询问第i号战舰与第j号战舰当前是否处于同一列中,如果在同一列中,它们之间间隔了多少艘战舰。
战舰数目N≤30000,指令数目T≤500000
题解:
本题要求去距离,因此可以考虑并查集的边带权处理
设d[x]为x到根fa[x]的距离,则每次把集合x和集合y合并时,需要知道y集合的大小,因此需要维护一个size[y]来表示y集合的大小。每次合并时,更新d[x]为size[y],然后压缩路径
代码:

#include <bits/stdc++.h>

using namespace std;

int t, n;
char str[3];

int const N = 3e4 + 10;
int fa[N], size[N];
int d[N];  // d[i]表示i到fa[i]的距离

// 查询+路径压缩+更新边权
int get(int x) {
   
    if (x == fa[x]) return x;
    int root = get(fa[x]);  // 找到根,并不断更新d数组
    d[x] += d[fa[x]];
    return fa[x] = root;
}

int main() {
   
    cin >> t;
    
    // 初始化
    for (int i = 1; i <= 30000; ++i) fa[i] = i, size[i] = 1;
    while (t--) {
   
        int a, b;
        scanf("%s%d%d", str, &a, &b);
        
        // 合并操作
        if (str[0] == 'M') {
   
            a = get(a), b = get(b);
            d[a] = size[b];  // 计算a到b的距离
            size[b] += size[a];  // 更新b的大小
            fa[a] = b;  // 更新a的父节点
        }
        else {
   // 询问操作
            if (get(a) != get(b)) printf("-1\n");
            else printf("%d\n", max(0, abs(d[a] - d[b]) - 1));
        }
    }
    return 0;
}

acwing239奇偶游戏
题意:
小A和小B在玩一个游戏。首先,小A写了一个由0和1组成的序列S,长度为N。然后,小B向小A提出了M个问题。
在每个问题中,小B指定两个数 l 和 r,小A回答 S[l~r] 中有奇数个1还是偶数个1。机智的小B发现小A有可能在撒谎。
例如,小A曾经回答过 S[1~3] 中有奇数个1, S[4~6] 中有偶数个1,现在又回答 S[1~6] 中有偶数个1,显然这是自相矛盾的。请你帮助小B检查这M个答案,并指出在至少多少个回答之后可以确定小A一定在撒谎。
即求出一个最小的k,使得01序列S满足第 1~k个回答,但不满足第 1~k+1个回答。
题解: 本题告诉l~r之间的奇数个数,其实是告诉了S[l - 1]与S[r]的奇偶关系(S[i]为1~i之间的1的个数)。那么本题就只有2种状态,即0和1;
可以使用边带权的并查集,维护一个d[x]表示x与fa[x]的奇偶关系。如果d[x]为0,那么x与fa[x]的奇偶性相同;否则不同
代码:

#include<bits/stdc++.h>
using namespace std;

int const N = 2e4 + 10;
int cnt = 1;
unordered_map<int, int> S;
int n, m;
int fa[N], d[N];

// 离散化
int mapping(int x) {
   
    if (!S.count(x)) S[x] = cnt++;
    return S[x];
}

int get(int x) {
   
    if (x == fa[x]) return x;
    int root = get(fa[x]);
    d[x] ^= d[fa[x]];
    return fa[x] = root;
}

int main() {
   
    cin >> n >> m;
    for (int i = 1 ; i <= 2 * m; ++i) fa[i] = i;
    int res = m;
    for (int i = 1; i <= m; ++i) {
   
        int a, b;
        string type;
        cin >> a >> b >> type;
        int t = 0;
        if (type == "odd") t = 1;
        a = mapping(a - 1), b = mapping(b);  // 离散化
        int pa = get(a), pb = get(b);  // 找父节点
        if (pa != pb) {
   // 父节点不同,更新距离
            fa[pa] = pb;
            d[pa] = d[a] ^ d[b] ^ t;
        }
        else {
   // 父节点相同,判断是否奇偶性相同
            if (d[a] ^ d[b] != t) {
   
                res = i - 1;
                break;
            }
        }
    }
    cout << res << endl;
    return 0;
}

acwing240食物链
题意:
A吃B,B吃C,C吃A,一共有N个动物,给出K个关系,问其中有多少关系是假的。若D=1,则表示X和Y是同类。若D=2,则表示X吃Y。
1≤N≤50000,0≤K≤100000
题解: 设置一个共同的起点,d[x]表示x到起点的距离,距离只有%3后才有意义。
那么X吃Y就是(d[x] - d[y]) % 3 == 1, X和Y同类就是(d[x] - d[y]) % 3 == 0,
d的更新关系为:
1.在做get操作时:d[x] += d[fa[x]];
2.在合并时:d[px]= ((t + d[y] - d[x]) % 3 + 3) % 3
代码:

#include <bits/stdc++.h>

using namespace std;

int const N = 5e4 + 10;
int n, k, fa[N], d[N];

int get(
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值