前几天刷了好多并查集的题,感觉这个是比较简单易懂但又很重要的知识点
上题:
2.P1551 亲戚
并查集总共就两个操作,合并和查询
每个集合中有一个代表元素,作为每个集合的祖先
首先定义一个f数组,表示某个点的父亲是谁
举个例子
int f[10];
f[1]=2;
表示1号的父亲是2;
find函数就是查询某个点的父亲是谁
int find(int x)
{
if(f[x]==x) return x;
else return find(x);
}
合并操作
f[x]=y;
就是将x的父亲的父亲设为y的父亲,x就属于y集合
比如1的父亲是2,2的父亲是他本身,3的父亲是4,4的父亲是他本身
f[1]=2;
f[2]=2;
f[3]=4;
f[4]=4;
1和2 ,3和4分别是两个不同的集合
合并之后就是
1的父亲是2,2的父亲是4,3的父亲是4,4的父亲是他本身
这样,1的爷爷就是4,2的父亲是4,3的父亲是4,所以1,2,3,4属于同一集合
但是
这样查询起来就会特别麻烦,特别是数据量过大的时候
换个角度想想,我们关心的仅仅只是某个元素是否属于某个集合,也就是关心某个元素的祖先是谁,所以可以对find函数进行路径压缩,
int find(int x)
{
if(f[x]==x) return x; //如果f[x]==x,代表找到祖先
else return f[x]=find(f[x]);//否组就继续找f[x]的祖先
}
如果是没有路径压缩的find函数,查找就会是这样子
路径压缩后就是这样
这样,在不断向上查询过程中,将祖先不断更新,最后查询已经压缩过的元素就只需要一步find,不用多次递归。
1.P3367 【模板】并查集
模板题,直接看代码
#include<bits/stdc++.h>
using namespace std;
int n,m;
int f[200000];//较大的数组开成全局变量,避免爆栈
int find(int k)//路径压缩
{
if(f[k]==k) return k;
else return f[k]=find(f[k]);
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
f[i]=i;//初始化,指定每个元素的祖先为他自己
}
for(int i=1;i<=m;i++)
{
int z,x,y;
cin>>z>>x>>y;
if(z==1)
{
f[find(x)]=find(y);//查询,并压缩
}
else
{
if(find(x)==find(y)) cout<<"Y"<<endl;
else cout<<"N"<<endl;
}
}
}
2.P1551 亲戚
也是模板
#include<bits/stdc++.h>
using namespace std;
int n,m,p,f[100000],a,b,c,d;
int find(int x)
{
if(x==f[x])
{
return x;
}
else
{
return f[x]=fd(f[x]);
}
}
void hd(int x,int y)
{
f[find(y)]=find(x);
}
int main()
{
cin>>n>>m>>p;
for(int i=1;i<n;i++)
f[i]=i;
for(int i=1;i<=m;i++)
{
cin>>a>>b;
hd(a,b);
}
for(int i=1;i<=p;i++)
{
cin>>c>>d;
if(find(c)==find(d))
cout<<"Yes"<<endl;
else
cout<<"No"<<endl;
}
}
3.P1111 修复公路
#include<bits/stdc++.h>
using namespace std;
int n,m;
struct c
{
int x,y,t;
}ci[2000000];//定义结构体,x代表起点,y代表终点
bool cmp(c a,c b)//cmp函数,使sort按照时间排序
{
return a.t<b.t;
}
int f[2000000];
int find(int k)//路径压缩
{
if(f[k]==k) return k;
else return f[k]=find(f[k]);
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
f[i]=i;
for(int i=1;i<=m;i++)
{
cin>>ci[i].x>>ci[i].y>>ci[i].t;
}
sort(ci+1,ci+m+1,cmp);
for(int i=1;i<=m;i++)
{
int fx=find(ci[i].x);
int fy=find(ci[i].y);
if(fx!=fy)
{
f[find(ci[i].x)]=find(ci[i].y);
//如果某两个点还没连接起来,那就连接起来,并且总结点数-1,代表尚未连接的点还有n-1个
n--;
}
if(n==1) //如果只剩最后一个点没有连接,那就输出这两个点连接所需要的时间
{
cout<<ci[i].t;
return 0;//输出就了就直接结束程序
}
}
cout<<-1;//如果所有的都连接起来还没联通,就输出-1
return 0;
}
最后附上暑假算法刷题记录:gitee仓库
按照完成方法重新写了算法标签(吐槽一下洛谷的算法标签)