问题描述
请实现一个管理互质动态集合S = {S1, S2, …, Sk}的程序。
首先读取整数n,创建由0, 1, …, n-1这样n个互不相同的元素组成的集合。
然后读取整数q,对集合进行q个查询操作。查询包含以下2种:
unite(x, y):合并包含x的集合
S
x
S_x
Sx与包含y的集合
S
y
S_y
Sy
same(x, y):判断x与y是否包含于同一集合
输入:
n q
com1 x1 y1
com2 x2 y2
…
comq xq yq
第1行指定n和q。接下来q行指定查询操作。comi 代表查询的种类,“0”为 unite,“1”为 same。
输出: 各same查询中,x与y包含于同一集合时返回1,否则返回0,每个结果占1行。
限制:
1 ≤ n ≤ 10000
1 ≤ q ≤ 100000
输入示例
5 12
0 1 4
0 2 3
1 1 2
1 3 4
1 1 4
1 3 2
0 1 3
1 2 4
1 3 0
0 0 4
1 0 2
1 3 0
输出示例
0
0
1
1
1
0
1
1
讲解
Disjoint Sets 是一种用互质的集合(一个元素不同时包含于多个集合的集合)对数据进行分类管理的数据结构。这种数据结构可以有效地动态处理以下操作:
makeSet(x):创建仅包含元素x的新集合
findSet(x):求包含元素x的集合的“代表”元素
unite(x, y):合并指定的元素x、y
我们用findSet(x)可以找出元素x包含于哪个集合。再Disjoint Sets中,查询“指定的两个元素x、y是否包含于同一集合”的操作称为Union Find。
这里我们用名为Disjoint Sets Forests的森林结构来实现Disjoint Sets。本书中,我们将树的集合称为森林。构成森林的树代表各个集合,树的各结点代表集合内的各元素。
我们将各个树的根结点用作区分集合的代表元素。因此,findSet(x)将返回元素x所属树(集合)的根结点的值。为找出根结点,我们要让各结点具备指向其父结点的指针,以便从任意一结点都可以查询到根结点。另外,代表元素的指针要指向其自身。
findSet(x)的复杂度等于各结点到代表元素之间所经历的指针数,即树的高度。这里除了求代表元素之外,我们让findSet(x)再具备路径压缩的功能,从而提高后续findSet(x)的执行效率。所谓路径压缩,就是在求代表元素的同时,对起始元素到代表元素的路径上的所有结点进行修改,使结点的指针全部指向代表元素。
至于合并指定元素x、y的操作unite(x, y),我们要在x的代表元素和y的代表中选出一个作为新的代表元素,同时将另一个代表元素的指针指向新代表元素。
这里的关键问题是如何选出新代表元素。我们用表示各集合的树的高度作为判断依据。将以各结点x为根的树的高度计入变量rank[x]。初始状态下每个集合中都只有一个元素,因此rank[x]的值全部为0。
在合并集合时,如果两个集合的树的高度不同,则将较低的树合并至较高的树中(以防止新树比原树更高)。合并高度相同的树时,合并后新代表元素的rank增加1。
AC代码如下
#include<iostream>
#include<vector>
using namespace std;
class DisjointSet{
public:
vector<int> rank, p;
DisjointSet() {}
DisjointSet(int size){
rank.resize(size, 0);
p.resize(size, 0);
for(int i = 0; i < size; i++) makeSet(i);
}
void makeSet(int x){
p[x] = x;
rank[x] = 0;
}
bool same(int x, int y){
return findSet(x) == findSet(y);
}
void unite(int x, int y){
link(findSet(x), findSet(y));
}
void link(int x, int y){
if(rank[x] > rank[y]){
p[y] = x;
} else {
p[x] = y;
if(rank[x] == rank[y]){
rank[y]++;
}
}
}
int findSet(int x){
if(x != p[x]){
p[x] = findSet(p[x]);
}
return p[x];
}
};
int main(){
int n, a, b, q;
int t;
cin>>n>>q;
DisjointSet ds = DisjointSet(n);
for(int i = 0; i < q; i++){
cin>>t>>a>>b;
if(t == 0) ds.unite(a, b);
else if(t == 1){
if(ds.same(a, b) ) cout<<1<<endl;
else cout<<0<<endl;
}
}
return 0;
}