并查集
这个专辑之前写在洛谷上,但年久失修,所以我决定在博客上重写一遍
正文
定义
并查集,顾名思义,是关于合并以及查询的集合,实现为一个森林,每一棵树是一个集合。支持两种操作,合并以及查询,合并是指将两个元素所在的两个树合并,查询则是查找一个元素所在树的根结点。
初始化
在没有任何操作进行的时候,所有元素所在树的根结点都是自己
for(int i=1;i<=n;i++)
{
fa[i]=i;
}
查询
对于每个要查询的结点,向上寻找 \(fa[x]\),直到某一层上 \(fa[x]=x\) 为止。答案就是当时的 \(x\)。这部分通常用递归的方式完成
int find(int x)
{
if(fa[x]==x) return x;
return find(fa[x]);
}
合并
路径压缩
由于我们只关心每颗树根结点的信息,所以直接让一棵树中的所有点全部连接到根结点上,即为路径压缩。
这一步类似于记忆化搜索,只需要在查询时让 \(fa[x]\) 指向根节点即可。
int find(int x)
{
if(fa[x]==x) return x;
return fa[x]=find(fa[x]);
}
合并操作
同理,由于我们只关心每颗树根结点的信息,直接让 \(A\) 子树的根结点连到 \(B\) 子树的根结点即可。
void merge(int x,int y)
{
int fx=find(x),fy=find(y);
fa[fx]=fy;
}
Luogu P3367
并查集模板题仅需上述两种操作即可通过
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int fa[N];
int find(int x)
{
if(x==fa[x])
{
return x;
}
return fa[x]=find(fa[x]);
}
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
fa[i]=i;
}
while(m--)
{
int op,x,y;
cin>>op>>x>>y;
if(op==1)
{
fa[find(x)]=find(y);
}
else
{
int fx=find(x),fy=find(y);
if(fx==fy)
{
cout<<"Y"<<endl;
}
else
{
cout<<"N"<<endl;
}
}
}
return 0;
}
Luogu P1111
简述题意
\(n\) 个点,\(m\) 条未连接的边,每条边在第 \(t_i\) 时间可以通车,求最少需要多少时间能使任意两点通车
解法
因为我们要求最小时间,可以把边按时间排序,并循环 \(n\) 次,每次对并查集进行一次合并操作,直到所有点都在一个集合为止。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int fa[N],sz[N];
int find(int x)
{
if(x==fa[x])
{
return x;
}
return fa[x]=find(fa[x]);
}
struct node
{
int u,v,w;
void read()
{
cin>>u>>v>>w;
}
}g[N];
bool cmp(node n1,node n2)
{
return n1.w<n2.w;
}
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++)
{
fa[i]=i;
sz[i]=1;
}
for(int i=1;i<=m;i++)
{
g[i].read();
}
sort(g+1,g+m+1,cmp);
for(int i=1;i<=m;i++)
{
int u=g[i].u,v=g[i].v,w=g[i].w;
int fu=find(u),fv=find(v);
if(fu==fv)
{
continue;
}
fa[fu]=fv;
sz[fv]+=sz[fu];//用sz数组统计每个集合的元素数量
if(sz[fv]==n)
{
cout<<w<<endl;
return 0;
}
}
cout<<-1<<endl;
return 0;
}

浙公网安备 33010602011771号