【模板】带权并查集

1. 奇偶游戏

239. 奇偶游戏


在这里插入图片描述


题意: 依次给出多个区间的含 1 1 1 的个数的奇偶性,找出第一个不符合的答案的回答。

思路

已知区间 [ a , b ] [a,b] [a,b] [ b , c ] [b,c] [b,c]的奇偶性,那么具有传递性,即 [ a , c ] [a,c] [a,c]区间的奇偶性也已知了。(奇偶都指区间内 1 1 1的个数)

定义:区间奇为1,区间偶为0。

  • 那么传递性可表示为:

    • 1 + 1 − > 0 1+1->0 1+1>0
    • 1 + 0 − > 1 1+0->1 1+0>1
    • 0 + 1 − > 1 0+1->1 0+1>1
    • 0 + 0 − > 0 0+0->0 0+0>0

以第一种举例,读者可带入【a,b】为奇,【b,c】为奇,那么【a,c】为偶。

如此发现,传递性形同于异或,即将给定的区间【a,b】的奇偶性,可视为【1,a-1】区间和【1,b】区间的奇偶性的异或结果,这是因为异或的前缀和性质,读者可自行了解(简单理解: x x x xor x x x = 0 =0 =0)。所以给定一个区间【a,b】的奇偶性,实际上可以转化为 A − 1 A-1 A1 B B B的奇偶性。( 大写 A A A 定义为前缀和序列:【1,a】区间)

由上转化,我们需要维护的是前缀和序列的奇偶性:

  • 【a,b】奇: A − 1 A-1 A1 B B B不同类。(01,10)
  • 【a,b】偶: A − 1 A-1 A1 B B B 同类。(00,11)

我们可以通过并查集维护这种具有连通性,传递性的关系。考虑如下:(为了书写简便,以下用A代替上述推导的A-1了)

在这里插入图片描述

定义边权 d [ u ] d[u] d[u]:

  1. d [ u ] = = 0 d[u]==0 d[u]==0,表示 u u u 与父节点 p [ u ] p[u] p[u] 奇偶性同。
  2. d [ u ] = = 1 d[u]==1 d[u]==1,表示 u u u 与父节点 p [ u ] p[u] p[u] 奇偶性不同。

如图所示, A A A p [ A ] p[A] p[A] 同, p [ A ] p[A] p[A] r A rA rA 不同,那么如何推得 A A A 与根节点 r A rA rA 的关系也是不同呢?显然 d [ A ] d[A] d[A] xor d [ p [ A ] ] d[p[A]] d[p[A]] 就是答案。所以在路径压缩中,每个节点只需要做如上所述的异或运算即可得到和根节点的奇偶性关系。

那么在一个集合中,得到了每个节点和根节点的奇偶性关系,通过传递性,就可以知道该集合中的任意两个节点之间的关系了。

上述考虑完并查集的路径压缩以及同属一个集合之间节点的关系,现在考虑一下如何合并两个集合。

在这里插入图片描述

假设上图 A A A 集合和 B B B 集合合并, r B rB rB 连向 r A rA rA,那么图中 “ ? ” “?” ? 的边权是多少呢?考虑如下情况:

  • A A A B B B 同类:

    • ( d [ B ] d[B] d[B] xor ? ? ?) xor d [ A ] d[A] d[A] = = == == 0 0 0
  • A A A B B B 不同类:

    • ( d [ B ] d[B] d[B] xor ? ? ?) xor d [ A ] d[A] d[A] = = == == 1 1 1

= > => => ? ? ? = = == == t t t xor d [ A ] d[A] d[A] xor d [ B ] d[B] d[B] t t t 1 1 1 表示不同类, 0 0 0 表示同类)


所以对于给出的每个答案【a,b,t】(区间 [ a , b ] [a,b] [a,b],奇偶性 t : 0 / 1 t:0/1 t:0/1):

  • a − 1 a-1 a1 b b b 在一个集合:

    • 判断 d [ a − 1 ] d[a-1] d[a1] xor d [ b ] ! = t d[b] != t d[b]!=t 就是第一个不成立答案
  • a − 1 a-1 a1 b b b 不在一个集合:

    • 合并 a − 1 a-1 a1 集合连向 b b b 集合: p [ f i n d ( a − 1 ) ] = p [ f i n d ( b ) ] p[find(a-1)]=p[find(b)] p[find(a1)]=p[find(b)]
    • 并附上边权: d [ f i n d ( a − 1 ) ] = = d [ a − 1 ] d[find(a-1)]==d[a-1] d[find(a1)]==d[a1] xor d [ b ] d[b] d[b] xor t t t

读者可能会思考:上述判断不成立只有在同属于一个集合的情况下才会去判断,那么不属于一个集合然后合并的时候会不会存在矛盾呢?由于异或,由于传递性,是不会产生矛盾的。可以模拟一下各种情况。

代码

#include <iostream>
#include <cstring>
#include <algorithm>
#include <set> 

using namespace std;

const int N = 200010;

int n, m;
int p[N], d[N]; 
int a[N << 1];

int find(int x)  // 并查集
{
    if (p[x] != x) {
        int r = find(p[x]);
        // d[x] ^= d[p[x]]; // 路径压缩中的权值计算
        d[x] += d[p[x]]; d[x] %= 2;
        p[x] = r;
    }
    return p[x];
}

struct node {
    int a, b, c;
}; 

node f[N << 1]; 

/*
由于题目数据太大,所以这里需要离散化处理。
此程序采用set离散化,注意需要用到的值还包含x-1。
*/

int main()
{
    cin >> n >> m;
    for (int i = 0; i < N; i++) p[i] = i;
    set<int> s;
    for (int i = 0; i < m; i++) {
        int u, v; string c; cin >> u >> v >> c;
        int x;
        if (c == "odd") {
            x = 1;
        } else {
            x = 0;
        }
        f[i] = {u, v, x}; 
        s.insert(u), s.insert(v);
        s.insert(u - 1), s.insert(v - 1);
    }
    int idx = 0;
    for (auto c: s) a[++idx] = c;
    
    for (int i = 0; i < m; i++) {
        int u = f[i].a, v = f[i].b, c = f[i].c; 
        int A = lower_bound(a + 1, a + 1 + idx, u - 1) - a, B = lower_bound(a + 1, a + 1 + idx, v) - a;
        int fx = find(A), fy = find(B);
        
        bool o = true; 
        
        if (fx == fy) {
            // if ((d[A] ^ d[B]) != c) o = false;
            if ((d[A] + d[B]) % 2 != c) o = false; 
        } else {
            p[fx] = fy; 
            // d[fx] = d[A] ^ d[B] ^ c;
            d[fx] = d[A] + d[B] + c;
        }
        
        if (!o) {
            cout << i << endl;
            return 0;
        }
    }
    cout << m << endl; 
    return 0;
}

这题还有扩展域做法,集合内属性维护的是条件,放在以后的并查集扩展域模板在做记录。


2. 银河英雄传说

238. 银河英雄传说


在这里插入图片描述


思路

  • s i z [ x ] siz[x] siz[x] 表示集合的大小, d [ x ] d[x] d[x] 表示 x x x p [ x ] p[x] p[x] 的距离。
  • a a a 列的船接在第 b b b 列的末尾,相当于让每个点到 p b pb pb 的距离都加上 s i z [ p b ] siz[pb] siz[pb]由于存储并查集中存在路径压缩的效果,因此只需要将 pa 到 pb 的距离加上 siz[pb] 即可,即 d [ p a ] = s i z [ p b ] d[pa] = siz[pb] d[pa]=siz[pb],其他跟着 p a pa pa 后面的元素会通过路径进行压缩更新 d [ i ] d[i] d[i] 的值。

代码

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 2e6 + 10;

int p[N], dis[N], siz[N];

int find(int x)  // 并查集
{
    if (p[x] != x) {
        int r = find(p[x]);
        dis[x] += dis[p[x]];
        p[x] = r;
    }
    return p[x];
}

int main()
{
    int n; scanf("%d", &n); 
    for (int i = 0; i < N; i++) siz[i] = 1, p[i] = i;
    int a, b; char op[2];
    for (int i = 0; i < n; i++) {
        scanf("%s%d%d", &op, &a, &b);
        int fx = find(a), fy = find(b);
        if (op[0] == 'M') {
            if (fx != fy) {
                p[fx] = fy;
                dis[fx] = siz[fy];
                siz[fy] += siz[fx];
            }
        } else {
            if (fx != fy) { printf("-1\n"); }
            else printf("%d\n", max(0, abs(dis[b] - dis[a]) - 1));
        }
    }
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ღCauchyོꦿ࿐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值