【基础算法】并查集

对一些学过的算法模板进行总结

并查集

0x01:概念

并查集是一种可以动态维护若干个不重叠的结合,并且支持合并与查询的数据结构。
也就是擅长维护各种各样的具有传递的关系。

0x02:基本操作

  1. find(int x)操作 找x的祖宗结点
  2. merge(int x, int y)操作 把两个集合合并并为一个集合

0x03:模板

// 初始化,每个点都是一个独立的集合,且祖宗是本身
for(int i = 1; i <= n; ++i) p[i] = i;

//路径压缩:每一次运行find操作,都会把访问过的结点都指向祖宗。这种方法可以避免出题人刻意卡掉链式结构。复杂度O(logn)
int find(int x){
	return x == p[x] ? x : p[x] = find(p[x];
}

void merge(int x, int y){
	p[find(x)] = find(y);
}

题目总结

0x01:格子游戏

【题目】
Alice和Bob玩了一个古老的游戏:首先画一个 n×n 的点阵(下图 n=3 )。
接着,他们两个轮流在相邻的点之间画上红边和蓝边:
在这里插入图片描述
直到围成一个封闭的圈(面积不必为 1)为止,“封圈”的那个人就是赢家。
因为棋盘实在是太大了,他们的游戏实在是太长了!
他们甚至在游戏中都不知道谁赢得了游戏。
于是请你写一个程序,帮助他们计算他们是否结束了游戏?

【输入格式】
输入数据第一行为两个整数 n 和 m。
n表示点阵的大小,m 表示一共画了 m 条线。
以后 m 行,每行首先有两个数字 (x,y),代表了画线的起点坐标,接着用空格隔开一个字符,假如字符是 D,则是向下连一条边,如果是 RR 就是向右连一条边。
输入数据不会有重复的边且保证正确。
【输出格式】
输出一行:在第几步的时候结束。
假如 m 步之后也没有结束,则输出一行“draw”。

【解题思路】
题意的围成环这个条件,可以读出元素之间的连接性,可以使用并查集解题。
将每个点存入并查集内,
还有二维坐标转一维的用法。

【代码】

#include<iostream>

using namespace std;

const int N = 40010;

int p[N];
int n, m;
int get(int a, int b){ // 将二维降成一维
    return a * n + b;
}

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

int main(){
    cin >> n >> m;
    
    for(int i = 0; i < n * n; ++i) p[i] = i; // 初始化
    
    int res = 1;
    while(m --){
        int a, b;
        char op[2];
        
        scanf("%d%d%s", &a, &b, op);
        
        a--;
        b--;
        
        int x = get(a, b);
        int y;
        if(*op == 'D') y = get(a + 1, b);
        else y = get(a, b + 1);
        
        if(find(x) == find(y)){
            cout << res << endl;
            break;
        }
        
        p[find(x)] = find(y);
        res++;
    }
    
    if( m == -1){ // 注意用while退出的时-1
        cout << "draw" << endl;
    }
    return 0;
}

0x02:程序自动机

【题目】
在实现程序自动分析的过程中,常常需要判定一些约束条件是否能被同时满足。
考虑一个约束满足问题的简化版本:假设x1,x2,x3,…代表程序中出现的变量,给定n个形如xi=xj或xi≠xj的变量相等/不等的约束条件,请判定是否可以分别为每一个变量赋予恰当的值,使得上述所有约束条件同时被满足。
例如,一个问题中的约束条件为:x1=x2,x2=x3,x3=x4,x1≠x4,这些约束条件显然是不可能同时被满足的,因此这个问题应判定为不可被满足。
现在给出一些约束满足问题,请分别对它们进行判定。

【输入格式】
输入文件的第1行包含1个正整数t,表示需要判定的问题个数,注意这些问题之间是相互独立的。
对于每个问题,包含若干行:
第1行包含1个正整数n,表示该问题中需要被满足的约束条件个数。
接下来n行,每行包括3个整数i,j,e,描述1个相等/不等的约束条件,相邻整数之间用单个空格隔开。若e=1,则该约束条件为xi=xj;若e=0,则该约束条件为xi≠xj。
【输出格式】
输出文件包括t行。
输出文件的第k行输出一个字符串“YES”或者“NO”(不包含引号,字母全部大写),“YES”表示输入中的第k个问题判定为可以被满足,“NO”表示不可被满足。

【数据范围】

1≤n≤1000000
1≤i,j≤1000000000

【输入样例】

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

【输出样例】

NO
YES

【解题思路】
等号的数存入并查集内.
使用离散化处理输入的数据,因为数值的范围过大,使用那么大的数组会浪费很多空间。
要先将所有情况保存,再先对等号的情况做出处理,再接着对不等号的情况做出判断得出结果。
【代码】

#include<iostream>
#include<vector>
#include<unordered_map>

using namespace std;

const int N = 1000010;

typedef pair<int, int> PII;

int p[N * 2];
int n, cnt;
vector<PII> eqs, ueqs;
unordered_map<int, int> H;

int mapping(int x){
    if(H.count(x)) return H[x];
    return H[x] = cnt++;
}

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

int main(){
    int T;
    cin >> T;
    while(T -- ){
        cin >> n;
        cnt = 0;
        eqs.clear();
        ueqs.clear();
        H.clear();
        
        for(int i = 0; i < n; ++i){
            int a, b, c;
            cin >> a >> b >> c;
            a = mapping(a);
            b = mapping(b);
            if(c) eqs.push_back({a, b});
            else ueqs.push_back({a, b});
        }
        
        for(int i = 0; i < cnt; ++i) p[i] = i;
        
        
        for(auto x : eqs){
            p[find(x.first)] = find(x.second);
        }
        
        bool res = true;
        for(auto x : ueqs){
            if(find(x.first) == find(x.second)){
                res = false;
                break;
            }
        }
        
        printf("%s\n", res ? "YES" : "NO");
    }
    
    return 0;
}

0x03:连通块中点的数量

【题目】
给定一个包含n个点(编号为1~n)的无向图,初始时图中没有边。
现在要进行m个操作,操作共有三种:
“C a b”,在点a和点b之间连一条边,a和b可能相等;
“Q1 a b”,询问点a和点b是否在同一个连通块中,a和b可能相等;
“Q2 a”,询问点a所在连通块中点的数量;

【输入格式】
第一行输入整数n和m。
接下来m行,每行包含一个操作指令,指令为“C a b”,“Q1 a b”或“Q2 a”中的一种。
【输出格式】
对于每个询问指令”Q1 a b”,如果a和b在同一个连通块中,则输出“Yes”,否则输出“No”。
对于每个询问指令“Q2 a”,输出一个整数表示点a所在连通块中点的数量
每个结果占一行。

【数据范围】

1≤n,m≤105

【输入样例】

5 5
C 1 2
Q1 1 2
Q2 1
C 2 5
Q2 5

【输出样例】

Yes
2
3

【代码】

#include<iostream>
#include<cstring>

using namespace std;

const int N = 100010;
int p[N];
int ps[N];
int n, m;

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

void lianjie(int x, int y){
    int a = find(x);
    int b = find(y);
    p[a] = b;
    ps[b] += ps[a];
}

int main(){
    cin >> n >> m;
    for(int i = 1; i <= n; ++i){
        p[i] = i;
        ps[i] = 1;
    }
    while(m --){
        string s;
        cin >> s;
        int a, b;
        
        if(s == "C"){
            cin >> a >> b;
            lianjie(a, b);
        }
        else if (s == "Q1"){
            cin >> a >> b;
            if( find(a ) == find(b)) {
                cout << "Yes" << endl;
            }
            else cout << "No" << endl;
        }
        else{
            cin >> a;
            cout << ps[find(a)] <<endl;
        }
    }
    
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值