加上路径压缩 并、查的时间复杂度都近似O(1)
并查集核心:1. 初始化 a[x]=x所有元素各自是一个集合 2. find(路径压缩优化)的函数 3. 合并集合、查询是否属于同一集合的操作
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int a[N];
int find(int x){
if(a[x]!=x) a[x]=find(a[x]);
return a[x];
}
int main(){
int n,m; cin >>n>>m;
for(int i=1;i<=n;i++){
a[i]=i;
}
string op;
while(m--){
cin>>op;
if(op=="M"){// 合并
int x,y;
cin>>x>>y;
a[find(x)]=find(y);
}
if(op=="Q"){// 查是否属于同一集合
int x,y;
cin>>x>>y;
if(find(x)==find(y)) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
}
}
加上记录集合元素数 则用size[N] 在合并时更新size
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int a[N],size[N]; 加size数组计数
int find(int x){
if(a[x]!=x) a[x]=find(a[x]);
return a[x];
}
int main(){
int n,m; cin >>n>>m;
for(int i=1;i<=n;i++){
a[i]=i;
size[i]=1; 初始每个集合大小为1
}
string op;
while(m--){
cin>>op;
if(op=="M"){// 合并
int x,y;
cin>>x>>y;
if(find(x)==find(y)) continue; 已属于一个集合 跳过
size[find(y)]+=size[find(x)]; size更新
a[find(x)]=find(y);
}
if(op=="Q"){// 查是否属于同一集合
int x,y;
cin>>x>>y;
if(find(x)==find(y)) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
}
}
eg 食物链 需要维护每个节点到根的距离
根据两个点到根的距离之差模三 来考虑这两点的关系(同类 吃 被吃)
这个距离并非真实距离 而是只考虑其“模3性”
具体如何维护距离:find中距离累加 + 合并操作中被合并的根节点要加新距离
#include<bits/stdc++.h>
using namespace std;
const int N=50000;
int a[N],d[N]; // d[N]维护到根节点距离
int find(int x){
if(a[x]!=x) {
int t=find(a[x]);// t先存着根
d[x]+=d[a[x]]; //求距离
a[x]=t; //最后再压缩路径
}
return a[x];
}
int main(){
int f=0;
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
a[i]=i;
}
while(m--){
int op,x,y;
cin>>op>>x>>y;
int px=find(x);// x的根
int py=find(y);// y的根
if(x>n||y>n){ // 大于N 错
f++;
continue;
}
if(op==2){
if(x==y) f++;// 自己吃自己 错
else{
//x y都在树中 那么可以知道其关系了 若x吃y 则 (d[x]-d[y]-1)%3=0(距离模3余1)
//若z吃x 按这种方式构造 z对y距离模3余2
//若m吃z 按这种方式构造 m对y模3余0 同类
if(px==py&&(d[x]-d[y]-1)%3) f++;
else if(px!=py){// x y不在一棵树 这句话就看成正确 那么要合并+构造符合要求的距离
a[px]=py; //合并
d[px]=d[y]-d[x]+1;//应有:(d[x]+d[px]-d[y]-1)%3=0 可推出d[px]
}
}
}
if(op==1){
if(px==py&&(d[x]-d[y])%3) f++;
else if(px!=py){
a[px]=py; //合并
d[px]=d[y]-d[x];//应有:(d[x]+d[px]-d[y])%3=0 可推出d[px]
}
}
}
cout<<f;
}
综上
(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)的偏移量