【数据结构】—— 并查集


并查集 

①合并两个集合

②查询某个元素的祖宗节点

查找 

通俗地讲一个故事:几个家族进行宴会,但是家族普遍长寿,所以人数众多。由于长时间的分离以及年龄的增长,这些人逐渐忘掉了自己的亲人,只记得自己的爸爸是谁了,而最长者(称为「祖先」)的父亲已经去世,他只知道自己是祖先。为了确定自己是哪个家族,他们想出了一个办法,只要问自己的爸爸是不是祖先,一层一层的向上问,直到问到祖先。如果要判断两人是否在同一家族,只要看两人的祖先是不是同一人就可以了。

在这样的思想下,并查集的查找算法诞生了。

路径压缩 

这样的确可以达成目的,但是显然效率实在太低。为什么呢?因为我们使用了太多没用的信息,我的祖先是谁与我父亲是谁没什么关系,这样一层一层找太浪费时间,不如我直接当祖先的儿子,问一次就可以出结果了。甚至祖先是谁都无所谓,只要这个人可以代表我们家族就能得到想要的效果。把在路径上的每个节点都直接连接到根上,这就是路径压缩。

合并 

宴会上,一个家族的祖先突然对另一个家族说:我们两个家族交情这么好,不如合成一家好了。另一个家族也欣然接受了。
我们之前说过,并不在意祖先究竟是谁,所以只要其中一个祖先变成另一个祖先的儿子就可以了。

 


① 记录每个集合的大小 -》 绑定到根节点

② 记录每个点到根节点的距离 -》 绑定到每个点身上 


AcWing 1250. 格子游戏 

输入样例:

3 5
1 1 D
1 1 R
1 2 D
2 1 R
2 2 D

输出样例:

4

问题等价于:在一次次连边的过程中,什么时候第一次连成一个环 

可以将其看成是图论中的点跟边,出现环的时候,等价于,两个点在连通之前就已经在同一个连通块当中


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

using namespace std;

const int N = 400010;

int n, m;
int p[N];

// 坐标映射
int get(int x, int y)
{
    return x * n + y;
}

int find(int x)
{
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main()
{
    cin >> n >> m;
    
    for(int i = 1; i <= n * n; i ++ ) p[i] = i;
    
    int res = 0;
    for(int i = 1; i <= m; i ++ )
    {
        int x, y;
        char d;
        cin >> x >> y >> d;
        x --, y -- ;
        int a = get(x, y);
        int b;
        if(d == 'D') b = get(x + 1, y);
        else b = get(x, y + 1);
        
        int pa = find(a), pb = find(b);
        if(pa == pb)
        {
            res = i;
            break;
        }
        p[pa] = pb;
    }
    
    if(!res) puts("draw");
    else cout << res << endl;
    
    return 0;
}


AcWing 1252. 搭配购买 

输入样例:

5 3 10
3 10
3 10
3 10
5 100
10 1
1 3
3 2
4 2

输出样例:

1

 把每一个联通块看做是一个物品,然后就变成了一个01 背包问题了。


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

using namespace std;

const int N = 10010;

int n, m, vol;
int v[N], w[N];
int p[N];
int f[N];

int find(int x)
{
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main()
{
    cin >> n >> m >> vol;
    
    for(int i = 1; i <= n; i ++ ) p[i] = i;
    
    for(int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i];
    
    while(m -- )
    {
        int a, b;
        cin >> a >> b;
        int pa = find(a), pb = find(b);
        if(pa != pb)
        {
            v[pb] += v[pa];
            w[pb] += w[pa];
            p[pa] = p[pb];
        }
    }
    
    // 01背包问题
    for(int i = 1; i <= n; i ++ )
        if(p[i] == i)
            for(int j = vol; j >= v[i]; j -- )
                f[j] = max(f[j], f[j - v[i]] + w[i]);
    
    cout << f[vol] << endl;
    
    return 0;
}

AcWing 237. 程序自动分析 

输入样例:

2
2
1 2 1
1 2 0
2
1 2 1
2 1 1

输出样例:

NO
YES

思路
1.读入数据并加入离散化数组
2.离散化后去重,并用此数组的个数初始化并查集
3.按照先相等后不等的顺序,先维护所有的相等关系;最后在依次查看每一对不等关系是否出现矛盾

坑点
1.一开始的数组大小要开两倍
2.离散化之后注意映射是从0开始还是从1开始
3.并查集初始化有一个小技巧,初始化的数量直接是alls数组离散化去重之后的元素个数(下标从1开始映射)
4.离散化的查询和合并操作都应该在映射的数上进行操作


 

#include <cstring>
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <unordered_map>

using namespace std;

const int N = 2000010;

int n, m;
int p[N];
unordered_map<int, int> S;

struct Query
{
    int x, y, e;  
}query[N];

int get(int x)
{
    if(S.count(x) == 0) S[x] = ++ n;
    return S[x];
}

int find(int x)
{
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int main()
{
    int T; cin >> T;
    
    while(T -- )
    {
        n = 0;
        cin >> m;
        S.clear();
        for(int i = 0; i < m; i ++ )
        {
            int x, y, e;
            scanf("%d%d%d", &x, &y, &e);
            query[i] = {get(x), get(y), e};
        }
        
        for(int i = 1; i <= n; i ++ ) p[i] = i;
        
        // 合并所有的相等条件
        for(int i = 0; i < m; i ++ )
            if(query[i].e == 1)
            {
                int pa = find(query[i].x), pb = find(query[i].y);
                p[pa] = p[pb];
            }
            
        // 检查所有不等条件
        bool has_conflict = false;
        for(int i = 0; i < m; i ++ )
            if(query[i].e == 0)
            {
                int pa = find(query[i].x), pb = find(query[i].y);
                if(pa == pb)
                {
                    has_conflict = true;
                    break;
                }
            }
        if(has_conflict) puts("NO");
        else puts("YES");
    }
    return 0;
}

AcWing 238. 银河英雄传说 

输入样例:

4
M 2 3
C 1 2
M 2 4
C 4 2

输出样例:

-1
1

① 不问间隔多少战舰 -》 并查集

② 同时维护间隔多少战舰 -》 统一维护当前战舰到排头的距离

③ \left\{\begin{matrix}\left | d(x)-d(y) \right |,x !=y \\ 0,x=y \end{matrix}\right. 

d[x] 表示 x 到 p[x] 的距离


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

using namespace std;

const int N = 30010;

int m;
int p[N], sizes[N], d[N];

int find(int x)
{
    if(p[x] != x) 
    {
        int root = find(p[x]);
        d[x] += d[p[x]];
        p[x] = root;
    }
    return p[x];
}

int main()
{
    cin >> m;

    for(int i = 1; i < N; i ++ )
    {
        p[i] = i;
        sizes[i] = 1;
    }

    while(m -- )
    {
        char op[2];
        int a, b;
        scanf("%s%d%d", op, &a, &b);
        if(op[0] == 'M')
        {
            int pa = find(a), pb = find(b);
            if (pa != pb) { // 新加的,不在一个集合中才合并!!!
                d[pa] = sizes[pb]; // pa 到 pb 的距离为 pb 中节点的个数
                sizes[pb] += sizes[pa]; // 更新 pb 中节点的个数
                p[pa] = pb; // 将 pa 合并到 pb 中
            }
        }
        else
        {
            int pa = find(a), pb = find(b);
            if(pa != pb) puts("-1");
            else printf("%d\n", max(0, abs(d[a] - d[b]) - 1));
        }
    }
    return 0;
}

AcWing 239. 奇偶游戏 

输入样例:

10
5
1 2 even
3 4 odd
5 6 even
1 6 even
7 10 odd

输出样例:

3
来源:《算法竞赛进阶指南》, POJ1733 , kuangbin专题

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

using namespace std;

const int N = 20010;

int n, m;
int p[N], d[N];
unordered_map<int, int> S;

int get(int x)
{
    if (S.count(x) == 0) S[x] = ++ n;
    return S[x];
}

int find(int x)
{
    if (p[x] != x)
    {
        int root = find(p[x]);
        d[x] += d[p[x]];
        p[x] = root;
    }
    return p[x];
}

int main()
{
    cin >> n >> m;
    n = 0;

    for (int i = 0; i < N; i ++ ) p[i] = i;

    int res = m;
    for (int i = 1; i <= m; i ++ )
    {
        int a, b;
        string type;
        cin >> a >> b >> type;
        a = get(a - 1), b = get(b);

        int t = 0;
        if (type == "odd") t = 1;

        int pa = find(a), pb = find(b);
        if (pa == pb)
        {
            if (((d[a] + d[b]) % 2 + 2) % 2 != t)
            {
                res = i - 1;
                break;
            }
        }
        else
        {
            p[pa] = pb;
            d[pa] = d[a] ^ d[b] ^ t;
        }
    }

    cout << res << endl;

    return 0;
}

 

 

 

 

 

 

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

玄澈_

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

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

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

打赏作者

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

抵扣说明:

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

余额充值