并查集
模板题目:
P3367 【模板】并查集 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
输入样例:
4 7 2 1 2 1 1 2 2 1 2 1 3 4 2 1 4 1 2 3 2 1 4
输出样例:
N Y N Y
思路:
并查集一般使用在需要把一堆不相关的数据进行一定规则进行合并为一些集合的时候,一共有3
种操作 — — 1. 初始化 2. 进行查找 3. 进行合并。
初始化
一般初始化的时候将每一个值属于的集合都赋值为自己(可以设定一个数组来记录每一个值属于哪一个集合),初始化的意义是设定每一个数值属于一个集合,一共有N个集合,其中N表示数值的个数。
const int N = 1000010; int Fa[N]; // 表示每一个数属于那个集合 // 进行初始化 void Init(int n){ for(int i = 1;i <= n;i ++){ // 初始化n个值的集合为其本身 Fa[i] = i; } return ; }
进行查找
在查找的时候,需要找出给定任意数值属于哪一个集合,需要进行递归查找,如果找到数字
i
的集合是它本身,那么就直接返回i
,否则就继续寻找i
的属于那个一集合。int Find(int i){ // 查找i所属于的集合 if(Fa[i] == i){ // 如果i单独属于一个集合,那么i就是该集合的标识 return i; } return Find(Fa[i]); // 进行递归查找i所属于的标识 }
进行合并,对于给定参数
i,j
通常的做法是对i
和j
分别进行查找其属于那个集合(本质上要看属于那个集合,就是看两个集合的标志值是否一致),然后直接将j
属于的集合合并到i
所属于的集合中。// 进行合并 int united(int i, int j){ int fa_i = Find(i); int fa_j = Find(j); Fa[fa_j] = fa_i; return 0; }
最后将所有值归位到应属于的集合中,在进行查询的时候直接进行查找值的标志值是否一致,如果一致则说明这两个值是属于一个集合中的。
举例:
第一种情况:
对于
1 2 3 4
,我们将他们进行初始化,Fa[] = {1, 2, 3, 4}
,如果1 3 4
属于一个集合,那么会一次输入1 3
,3 4
,然后对于1 3
,会先找到1
和3
所属于的集合的标识,找到Fa[1] = 1, Fa[3] = 3
,那么令F[3] = F[1]
,得到Fa[] = {1, 2, 1, 4}
,这时得到1 3
都属于集合1
,2
属于集合2
,4
属于集合4
;然后进行处理3 4
,依然是首先找到3
和4
所属于的集合,分别得到1
和4
,令Fa[4] = Find(3)
,即F[4] = 3
,得到Fa[] = {1, 2, 1, 1}
,如此就得到了两种集合,分别是集合1 和 2
。另一种情况:
对于
1 2 3 4
,我们将他们进行初始化,Fa[] = {1, 2, 3, 4}
,如果1 3 4
属于一个集合,那么会一次输入3 4
,1 3
,然后对于3 4
,会先找到3
和4
所属于的集合的标识,找到Fa[3] = 3, Fa[4] = 4
,那么令F[4] = F[3]
,得到Fa[] = {1, 2, 3, 3}
,这时得到3 4
都属于集合3
,2
属于集合2
,1
属于集合1
;然后进行处理1 3
,依然是首先找到1
和3
所属于的集合,分别得到1
和3
,令Fa[3] = Find(1)
,即F[3] = 1
,得到Fa[] = {1, 2, 1, 3}
,对于这种情况就与第一种情况有所不同了,但是使用Find函数进行查找的时候依然找到的是Find(4) = 1
。简述一下整体过程:首先Find()
函数接收到参数4
,并判断出Fa[4] != 4
,于是进行向下进行递归,对Find()
传入参数Fa[4] = 3
,Fa[3] != 3
,继续向下递归,传入参数Fa[3] = 1
,Fa[1] == 1
,进行返回,最终得到数字4
所属于的集合是1
。有一种优化方案,可以使每一个值属于哪一个集合只需一步就能查出,称为 — — 压缩路径法
只需要在每次递归查找的时候如
Find(x)
,将x
的标志直接赋值为查询到的标志。int Find(int i){ // 查找i所属于的集合 if(Fa[i] == i){ // 如果i单独属于一个集合,那么i就是该集合的标识 return i; } Fa[i] = Find(Fa[i]); // 否则继续查找i所属于的集合的标识,并将i属于的集合直接等于其标识。 return Find(Fa[i]); // 进行递归查找i所属于的标识 }
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1000010;
int Fa[N]; // 表示每一个数属于那个集合
// 进行初始化
void Init(int n){
for(int i = 1;i <= n;i ++){ // 初始化n个值的集合为其本身
Fa[i] = i;
}
return ;
}
// 进行查找
int Find(int i){ // 查找i所属于的集合
if(Fa[i] == i){ // 如果i单独属于一个集合,那么i就是该集合的标识
return i;
}
Fa[i] = Find(Fa[i]); // 否则继续查找i所属于的集合的标识,并将i属于的集合直接等于其标识。
return Find(Fa[i]); // 进行递归查找i所属于的标识
}
// 进行合并
int united(int i, int j){
int fa_i = Find(i);
int fa_j = Find(j);
Fa[fa_j] = fa_i;
return 0;
}
void solve(){
int n,m;cin>>n>>m; // n个元素,m个操作
// 进行初始化
Init(n);
while(m--){
// 进行m次操作
int z, a, b;cin>>z>>a>>b;
if(z == 1){
united(a, b);
}else{
if(Find(a) == Find(b)){
cout<<"Y"<<endl;
}else cout<<"N"<<endl;
}
}
return ;
}
int main(){
ios::sync_with_stdio(0); // 进行优化使用
cin.tie(0);
int t = 1;
while(t--){
solve();
}
return 0;
}
练习题目 — — 合根植物:
[P8654 蓝桥杯 2017 国 C] 合根植物 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
输入样例:
5 4 16 2 3 1 5 5 9 4 8 7 8 9 10 10 11 11 12 10 14 12 16 14 18 17 18 15 19 19 20 9 13 13 17
输出样例:
5
思路:
常规的并查集练习,需要创建长度为
n
×
m
n \times m
n×m大小的数组,表示最初一共有
n
×
m
n \times m
n×m个集合,题目中给出1 < n,m < 1000
因此可以设定数组大小最大为1000010
,在初始化的时候根据具体输入的n
和 m
进行初始化。然后进行常规的合并,最后进行查找,可以利用一个哈希表进行记录每一个集合的标识,如果第一次找到该集合,那么就进行记录一次,否则就跳过,这样可以将查询的时间复杂度降低到
O
(
N
)
O(N)
O(N)。
代码:
// 合根植物
#include<bits/stdc++.h>
using namespace std;
const int N = 1000010;
int W[N]; // 作为哈希表,用于降低查找的时间复杂度
int Fa[N];
// 初始化
void initt(int n){
for(int i = 1;i <= n;i ++){
Fa[i] = i;
}
return ;
}
// 查找
int findd(int i){
if(Fa[i] == i){
return i;
}
Fa[i] = findd(Fa[i]);
return findd(Fa[i]);
}
// 合并
void unique(int i, int j){
int fai = findd(i);
int faj = findd(j);
Fa[faj] = fai;
return ;
}
void solve(){
int m,n;cin>>m>>n;
// 进行初始化
initt(n * m);
int k; cin>>k;
while(k--){
int a,b;
cin>>a>>b;
// 进行合并
unique(a, b);
}
// 进行统计
int ans = 0;
for(int i = 1;i <= n * m;i++){
int fai = findd(i);
if(!W[fai]){
W[fai]++;
ans++;
}
}
cout<<ans<<endl;
return ;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
int t = 1;
while(t--){
solve();
}
return 0;
}