并查集是一种用于维护元素所属集合的数据结构,本质是用双亲表示法实现的森林。其核心操作包含初始化、查询根节点、合并集合及判断元素是否同属一集合。通过路径压缩优化,查询与合并效率近似 O(1)。详细讲解并查集原理与 C++ 实现,提供标准模板,并结合亲戚关系判定、水塘计数及程序自动分析三个经典算法题进行实战演练,涵盖离散化技巧与约束条件处理,适合算法学习者参考。
规定:x 和 y 是亲戚,y 和 z 是亲戚,那么 x 和 z 也是亲戚。如果 x,y 是亲戚,那么 x 的亲戚都是 y 的亲戚,y 的亲戚也都是 x 的亲戚。
【输入格式】
第一行:三个整数 n, m, p,(n, m, p ≤ 5000),分别表示有 n 个人,m 个亲戚关系,询问 p 对亲戚关系。
以下 m 行:每行两个数 Mi, Mj,1 ≤ Mi, Mj ≤ n,表示 Mi 和 Mj 具有亲戚关系。
接下来 p 行:每行两个数 Pi, Pj,询问 Pi 和 Pj 是否具有亲戚关系。
#include<iostream>usingnamespace std;
constint N = 5010;
int pa[N];
int n, m, p;
voidinit(){
for(int i = 1; i <= n; i++) pa[i] = i;
}
intfind(int x){
if(x == pa[x]) return x;
return pa[x] = find(pa[x]);
}
voiduni(int x, int y){
int fx = find(x);
int fy = find(y);
pa[fx] = fy;
}
boolisSame(int x, int y){
returnfind(x) == find(y);
}
intmain(){
cin >> n >> m >> p;
init();
int x, y;
while(m--){
cin >> x >> y;
uni(x, y);
}
bool res;
while(p--){
cin >> x >> y;
res = isSame(x, y);
if(res) cout << "Yes" << endl;
else cout << "No" << endl;
}
return0;
}
2. Lake Counting S ⭐⭐
【题目描述】
由于最近的降雨,水在农夫约翰的田地里积聚了。田地可以表示为一个 N × M 的矩形(1 ≤ N ≤ 100;1 ≤ M ≤ 100)。每个方格中要么是水(W),要么是干地(.)。农夫约翰想要弄清楚他的田地里形成了多少个水塘。一个水塘是由连通的水方格组成的,其中一个方格被认为与它的八个邻居相邻。给定农夫约翰田地的示意图,确定他有多少个水塘。
#include<iostream>usingnamespace std;
constint N = 105;
char grid[N][N]; // 田地int pa[N * N]; // 并查集数组int n, m; // 右、右下、下、左下 4 个方向int dx[] = {1, 1, 0, -1};
int dy[] = {0, -1, -1, -1};
voidinit(){
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
// 把二维的坐标表示为一维的下标int t = (i - 1) * m + j;
// 如果是水坑,就当作一个集合if(grid[i][j] == 'W') pa[t] = t;
}
}
}
intfind(int x){
if(x == pa[x]) return x;
return pa[x] = find(pa[x]);
}
voiduni(int x, int y){
int fx = find(x);
int fy = find(y);
pa[fx] = fy;
}
intmain(){
cin >> n >> m;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
cin >> grid[i][j];
init();
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
if(grid[i][j] == '.') continue;
// 遍历 4 个方向for(int k = 0; k < 4; k++){
int x = i + dx[k];
int y = j + dy[k];
// 如果该方向没有越界并且也是水坑if(x > 0 && x <= n && y > 0 && y <= m && grid[x][y] == 'W'){
// 就把这个方向的位置和当前的位置合并在一起,注意坐标要转成一维uni((i - 1) * m + j, (x - 1) * m + y);
}
}
}
}
int cnt = 0; // 看有多少个根节点,就可以判断有多少个集合,也就是看有多少个元素的父节点指向自己for(int i = 1; i <= n * m; i++){
if(pa[i] == i) cnt++;
}
cout << cnt << endl;
return0;
}
3. 程序自动分析 ⭐⭐⭐
【题目描述】
在实现程序自动分析的过程中,常常需要判定一些约束条件是否能被同时满足。
考虑一个约束满足问题的简化版本:假设 x1, x2, x3, ... 代表程序中出现的变量,给定 n 个形如 xi = xj 或 xi ≠ xj 的变量相等/不等的约束条件,请判定是否可以分别为每一个变量赋予恰当的值,使得上述所有约束条件同时被满足。例如,一个问题中的约束条件为:x1 = x2, x2 = x3, x3 = x4, x4 ≠ x1,这些约束条件显然是不可能同时被满足的,因此这个问题应判定为不可被满足。
现在给出一些约束满足问题,请分别对它们进行判定。
【输入格式】
输入的第一行包含一个正整数 t,表示需要判定的问题个数。注意这些问题之间是相互独立的。
对于每个问题,包含若干行:
第一行包含一个正整数 n,表示该问题中需要被满足的约束条件个数。接下来 n 行,每行包括三个整数 i, j, e,描述一个相等/不等的约束条件,相邻整数之间用单个空格隔开。若 e = 1,则该约束条件为 xi = xj。若 e = 0,则该约束条件为 xi ≠ xj。
【输出格式】
输出包括 t 行。
输出文件的第 k 行输出一个字符串 YES 或者 NO(字母全部大写),YES 表示输入中的第 k 个问题判定为可以被满足,NO 表示不可被满足。