数据结构 ---- 并查集

发布于:2022-12-19 ⋅ 阅读:(356) ⋅ 点赞:(0)

并查集

用处

  1. 合并两个集合
  2. 查看两个元素是否是在一个集合之中

基本原理

每个集合用一棵树来表示,树根的编号是整个集合的编号。每个节点存储 x 的父节点,p [ x ] 表示 x 的父节点。

问题

  1. 如何判断树根 if ( p [ x ] == x )
  2. 求 x 的集合编号 while ( p [ x ] != x) x = p [ x ]
  3. 如何将两个集合合并 p [ x ] 是 x 的集合编号,p [ y ] 是 y 的集合编号 ;p [ x ] = y ;

模板

(1)朴素并查集:

    int p[N]; //存储每个点的祖宗节点

    // 返回x的祖宗节点 + 路径压缩
    int find(int x)
    {
        if (p[x] != x) p[x] = find(p[x]);
        return p[x];
    }

    // 初始化,假定节点编号是1~n
    for (int i = 1; i <= n; i ++ ) p[i] = i;

    // 合并a和b所在的两个集合:
    p[find(a)] = find(b);


(2)维护size的并查集:

    int p[N], size[N];
    //p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量

    // 返回x的祖宗节点
    int find(int x)
    {
        if (p[x] != x) p[x] = find(p[x]);
        return p[x];
    }

    // 初始化,假定节点编号是1~n
    for (int i = 1; i <= n; i ++ )
    {
        p[i] = i;
        size[i] = 1;
    }

    // 合并a和b所在的两个集合:
    size[find(b)] += size[find(a)];
    p[find(a)] = find(b);


(3)维护到祖宗节点距离的并查集:

    int p[N], d[N];
    //p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离

    // 返回x的祖宗节点
    int find(int x)
    {
        if (p[x] != x)
        {
            int u = find(p[x]);
            d[x] += d[p[x]];
            p[x] = u;
        }
        return p[x];
    }

    // 初始化,假定节点编号是1~n
    for (int i = 1; i <= n; i ++ )
    {
        p[i] = i;
        d[i] = 0;
    }

    // 合并a和b所在的两个集合:
    p[find(a)] = find(b);
    d[find(a)] = distance; // 根据具体问题,初始化find(a)的偏移量

如何理解 find ( ) 函数

p[x]中储存的是每个节点的父节点,一开始并不是树状的,而是一个个单独的节点,每个节点都是根节点,都有集合编号,然后通过不断的合并才形成的树状结构

例1.合并集合

一共有n个数,编号是1~n,最开始每个数各自在一个集合中。

现在要进行m个操作,操作共有两种:

  1. “M a b”,将编号为a和b的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;
  2. “Q a b”,询问编号为a和b的两个数是否在同一个集合中;

输入格式

第一行输入整数n和m。

接下来m行,每行包含一个操作指令,指令为“M a b”或“Q a b”中的一种。

输出格式

对于每个询问指令”Q a b”,都要输出一个结果,如果a和b在同一集合内,则输出“Yes”,否则输出“No”。

每个结果占一行。

数据范围

1≤n,m≤105

输入样例:

4 5
M 1 2
M 3 4
Q 1 2
Q 1 3
Q 3 4

输出样例:

Yes
No
Yes

在这里插入图片描述

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<stack>
#include<queue>
#include<sstream>
#include<map>

#define x first
#define y second

using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
const int N = 100000;
const int MOD = 1000000007;
const int INF = 0x3f3f3f3f;

int gcd(int a, int b){return b ? gcd(b, a % b) : a;}
int lowbit(int x) {return x & -x;}

int n, m;
// 存储父节点 
int f[N];

void init(){
	
	for(int i = 0; i < n; i ++ ){
		f[i] = i;
	}
	
}

// 核心操作 : 路径压缩
// 查询根节点的优化,将所有的父节点下面的点指向根节点 
int getf(int x){
	
	if(f[x] == x) return f[x];
	else{
		f[x] = getf(f[x]);
		return f[x];
	}
	
}

// 合并两个集合,x 集合的编号的父节点是 y 集合的根节点 
void merge(int x, int y){
	
	if(getf(x) != getf(y)) f[getf(y)] = getf(x);

}

int main()
{
	cin >> n >> m;
	init();
	for(int i = 0; i < m; i ++ ){
		char op;
		int x, y;
		cin >> op >> x >> y;
		if(op == 'M'){
			merge(x, y);
		}
		else if(op == 'Q'){
			if(getf(x) == getf(y)) cout << "Yes" << endl;
			else cout << "No" << endl;
		}
	}

	return 0;
}




例2.连通块中点的数量

给定一个包含n个点(编号为1~n)的无向图,初始时图中没有边。

现在要进行m个操作,操作共有三种:

  1. “C a b”,在点a和点b之间连一条边,a和b可能相等;
  2. “Q1 a b”,询问点a和点b是否在同一个连通块中,a和b可能相等;
  3. “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<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<stack>
#include<queue>
#include<sstream>
#include<map>

#define x first
#define y second

using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
const int N = 100000;
const int MOD = 1000000007;
const int INF = 0x3f3f3f3f;

int gcd(int a, int b){return b ? gcd(b, a % b) : a;}
int lowbit(int x) {return x & -x;}

int n, m;
int f[N], cnt[N];

void init(){
	
	for(int i = 1; i <= n; i ++ ){
		f[i] = i;
		cnt[i] = 1;
	}
	
}

int getf(int x){
	
	if(f[x] == x) return x;
	else{
		f[x] = getf(f[x]);
		return f[x];
	}
	
}

void merge(int x, int y){
	
	if(getf(x) != getf(y)){
	    //维护cnt数组的时候,一定要先维护,在进行合并
		cnt[getf(y)] += cnt[getf(x)];
		f[getf(x)] = getf(y);
	}
	
}

int main()
{
	cin >> n >> m;
	init();
	for(int i = 0; i < m; i ++ ){
		string op;
		int x, y;
		cin >> op;
		if(op == "C"){
			cin >> x >> y;
			merge(x, y);
		}
		else if(op == "Q1"){
			cin >> x >> y;
			if(getf(x) == getf(y)) cout << "Yes" << endl;
			else cout << "No" << endl; 
		}
		else if(op == "Q2"){
			cin >> x;
			cout << cnt[getf(x)] << endl;
		}
	}

	return 0;
}


AcWing 240.食物链

动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。

A吃B, B吃C,C吃A。

现有N个动物,以1-N编号。

每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。

有人用两种说法对这N个动物所构成的食物链关系进行描述:

第一种说法是”1 X Y”,表示X和Y是同类。

第二种说法是”2 X Y”,表示X吃Y。

此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。

当一句话满足下列三条之一时,这句话就是假话,否则就是真话。

1) 当前的话与前面的某些真的话冲突,就是假话;
2) 当前的话中X或Y比N大,就是假话;
3) 当前的话表示X吃X,就是假话。

你的任务是根据给定的N和K句话,输出假话的总数。

输入格式

第一行是两个整数N和K,以一个空格分隔。

以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。

若D=1,则表示X和Y是同类。

若D=2,则表示X吃Y。

输出格式

只有一个整数,表示假话的数目。

数据范围

1≤N≤50000,
0≤K≤100000

输入样例:

100 7
1 101 1 
2 1 2
2 2 3 
2 3 3 
1 1 3 
2 3 1 
1 5 5

输出样例:

3

代码样例

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<stack>
#include<queue>
#include<sstream>
#include<map>

#define x first
#define y second

using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
const int N = 100000;
const int MOD = 1000000007;
const int INF = 0x3f3f3f3f;

int gcd(int a, int b){return b ? gcd(b, a % b) : a;}
int lowbit(int x) {return x & -x;}

int n, k;
int f[N], cnt[N];

void init(){
	
	for(int i = 1; i <= n; i ++ ){
		f[i] = i;
	}
	
}

int getf(int x){
	
	if(f[x] == x) return x;
	else {
	    int t = getf(f[x]);
		cnt[x] += cnt[f[x]];
		f[x] = t;
		return f[x];
	}
	
}

int main()
{
	cin >> n >> k;
	init();
	int res = 0;
	while(k -- ){
		int d, x, y;
		cin >> d >> x >> y;
		//大于范围是谎话
		if(x > n || y > n) res ++ ;
		else{
			int px = getf(x), py = getf(y);
			if(d == 1){
				//同类吃同类是谎话
				if(px == py && (cnt[x] - cnt[y]) % 3) res ++ ;
				else if(px != py){
					f[px] = py;
					cnt[px] = cnt[y] - cnt[x];
				}
			}
			else{
				if(px == py && (cnt[x] - cnt[y] - 1) % 3) res ++ ;
				else if(px != py){
					f[px] = py;
					cnt[px] = cnt[y] + 1 - cnt[x];
				}
			}
		}
	}
	
	cout << res << endl;

	return 0;
}





网站公告

今日签到

点亮在社区的每一天
去签到