数据结构-并查集
非常基础简单有用的数据结构,可以合并两个集合、查询两个数是否在同一个集合,时间复杂度较玄学。
讲解:
在并查集中每个集合中的点之间的结构像一棵树,数组 f [ ] f[] f[] 表示一个节点的上司(父亲节点),如果 f [ x ] = = x f[x]==x f[x]==x 表示 x x x 是自己集合中的老板(根节点)。刚开始时每个人是一个集合,所以 f [ x ] = x f[x]=x f[x]=x。
void fset(int n){// 初始化
for(int i=1;i<=n;i++) f[i]=i;
}
因为 f [ x ] f[x] f[x] 只是表示 x x x 的上司,所以要找 x x x 的老板就是找 x x x 的上司的上司的上司的……直到那个上司 f [ f a ] = = f a f[fa]==fa f[fa]==fa。
int find(int x){//找x的上司
if(f[x]==x) return x;
return f[x]=find(f[x]);
//路径压缩,即让x的老板成为x的上司,这样以后找老板就方便了
}
合并 x x x 和 y y y 所在的集合,只需要让 x x x 的老板的上司为 y y y 的老板。代码如下:
void merge(int x,int y){
f[find(x)]=find(y);
}
判断 x x x 和 y y y 是否在同一个集合内,只需要看 x x x 的老板是否等于 y y y 的老板,如下:
void magnet(int x,int y){
if(find(x)==find(y)) puts("Y");
else puts("N");
}
如果你懂了,蒟蒻就放总代码了:
#include <bits/stdc++.h>
using namespace std;
const int N=1e4+10;
class Mergeset{
public:int f[N];
void fset(int n){
for(int i=1;i<=n;i++) f[i]=i;
}
int find(int x){
if(f[x]==x) return x;
return f[x]=find(f[x]);
}
void merge(int x,int y){
f[find(x)]=find(y);
}
void magnet(int x,int y){
if(find(x)==find(y)) puts("Y");
else puts("N");
}
}s;
int n,m;
int main(){
scanf("%d%d",&n,&m);
s.fset(n);
for(int i=1,x,y,z;i<=m;i++){
scanf("%d%d%d",&x,&y,&z);
if(x==1) s.merge(y,z);
else if(x==2) s.magnet(y,z);
}
return 0;
}
一次 f i n d ( ) find() find() 的时间复杂度为 Θ ( α ( n ) ) \Theta(\alpha(n)) Θ(α(n)),其中 α ( n ) < log n \alpha(n)<\log n α(n)<logn。
普通并查集讲完后,接下来是种类并查集。并查集可以维护关系的传递性和连通性,普通并查集的原理是“朋友的朋友是朋友”或“上司的上司是上司”,而种类并查集的原理是“敌人的敌人是朋友”或者更复杂。
例题:[NOIp提高组]关押罪犯
即给你 n n n 个点, m m m 条边,让你把所有点分成两份,使两端在同一份点的边的边权最大值最小。
讲解:
这题中输入每对罪犯关系以后,根据贪心思想,对每个关系按影响从大到小排序。如下:
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
scanf("%d%d%d",&em[i].a,&em[i].b,&em[i].h);
sort(em+1,em+m+1,cmp);
用 x = 1 ∼ n x=1\sim n x=1∼n 的节点表示罪犯 x x x 在第一个监狱,用 x = n + 1 ∼ 2 n x=n+1\sim 2n x=n+1∼2n 的节点表示罪犯 x − n x-n x−n 在第二个监狱。两个节点在同一个集合内表示两种情况一起发生。
刚开始时所有 2 n 2n 2n 个节点自己为一个集合(记得初始化!),然后枚举排序过后的关系,如果两个罪犯已经在同个监狱,答案就为两个罪犯之间的仇恨值。否则,把两个罪犯分在不同的监狱。如下:
for(int i=1;i<=m;i++){
if(s.magnet(em[i].a,em[i].b)||s.magnet(em[i].a+n,em[i].b+n))
{ans=em[i].h;break;} //如果两个罪犯已经在同个监狱
s.merge(em[i].a+n,em[i].b);
s.merge(em[i].a,em[i].b+n); //分到不同监狱
}
如果你懂了,蒟蒻就放代码了:
#include <bits/stdc++.h>
using namespace std;
const int N=4e4+10,M=1e5+10;
int n,m,ans;
class Em{public:int a,b,h;}em[M];
bool cmp(Em x,Em y){return x.h>y.h;}
class Mergeset{
public:int f[N];
void fset(int x){
for(int i=1;i<=x;i++) f[i]=i;
}
int find(int x){
if(f[x]==x) return x;
return f[x]=find(f[x]);
}
void merge(int x,int y){
f[find(x)]=find(y);
}
bool magnet(int x,int y){
return find(x)==find(y);
}
}s;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
scanf("%d%d%d",&em[i].a,&em[i].b,&em[i].h);
sort(em+1,em+m+1,cmp);
s.fset(2*n);
for(int i=1;i<=m;i++){
if(s.magnet(em[i].a,em[i].b)||s.magnet(em[i].a+n,em[i].b+n))
{ans=em[i].h;break;}
s.merge(em[i].a+n,em[i].b);
s.merge(em[i].a,em[i].b+n);
}
printf("%d\n",ans);
return 0;
}
种类并查集相关题目推荐:[NOI2001]食物链
并查集的后续知识有可持久化并查集。
祝大家学习愉快!