并查集 学习笔记
此篇笔记是我从自己的洛谷博客上搬运而来。更多的是偏向于做题的总结。
前言:简而言之,并查集是一种数据结构,带有一些限定条件,能够帮助计算机在很大的数据范围里很快得出结果。
此算法可以理解为”父亲“和”儿子“的关系。一个父亲可以有多个儿子,每个儿子只有一个父亲。
初始化:fa[i]=i。每个节点一开始的父亲都是他自己。
查找函数
int find(int x){if ((if (fa[x]==x) return x;return fa[x]=find(fa[x]);}
这里需要路径压缩,为了更快的找出一个节点的父亲。
合并函数
void merge(int x,int y) { int xx=find(x),yy=find(y); if(xx!=yy) fa[xx]==yy; }
到这里,并查集的基础知识已经结束了。根据我的个人理解,并查集可以在多种类型的题目中出现,是一种非常有用的算法。很多图论题也可以用此算法AC。
----------------------------------------------
T1 修复公路
并查集裸题,其实就是最小生成树。
#include<bits/stdc++.h> using namespace std; int father[100005],cnt[100005]; struct node { int x,y,t; }s[100005]; int cmp(node s,node y) { return s.t<y.t; } int find (int x) { if (x==father[x]) return x; return find(father[x]);; } int unionn(int r1,int r2) { father[r2]=r1; } int main() { int n,m; cin>>n>>m; for (int i=1;i<=n;i++) father[i]=i,cnt[i]=1; for (int i=1;i<=m;i++) { cin>>s[i].x>>s[i].y>>s[i].t; } sort(s+1,s+m+1,cmp); for (int i=1;i<=m;i++) { int r1=find(s[i].x),r2=find(s[i].y); if (r1!=r2) unionn(r1,r2),cnt[r1]+=cnt[r2]; if (cnt[r1]==n){ cout<<s[i].t; return 0; } } cout<<-1; return 0; }
T2 关押罪犯
一道很经典的题目。正解有两种,一种是二分图,另一种是并查集。
这里我们要引入”补集“思想。正所谓敌人的敌人就是朋友。所以面对”敌人“,我们只需要将其和”敌人的敌人“进行合并就行了。
#include<bits/stdc++.h> using namespace std; int f[200005]; struct node { int a,b,c; }s[100005]; int cmp(node x,node y) { return x.c>y.c; } int find (int x) { if (x==f[x]) return x; return find(f[x]); } int main() { int n,m; cin>>n>>m; for (int i=1;i<=n*2;i++) f[i]=i; for (int i=1;i<=m;i++) cin>>s[i].a>>s[i].b>>s[i].c; sort(s+1,s+m+1,cmp); for (int i=1;i<=m;i++) { int r1=find(s[i].a); int r2=find(s[i].b); if (r1==r2){ cout<<s[i].c;return 0; } f[r2]=find(s[i].a+n); f[r1]=find(s[i].b+n); } cout<<0; return 0; }
T3 食物链
同样可以运用”补集“思想。这道题有三类物种:天敌,自己,猎物。在合并的时候还要注意是否矛盾,不能出现“自己的猎物是天敌”的情况。
#include<bits/stdc++.h> using namespace std; int father[150005],ans; int find (int x) { if(x==father[x]) return x; return father[x]=find(father[x]); } inline void uni(int r1,int r2) { father[find(r2)]=find(r1); } int main() { int n,m; cin>>n>>m; for (int i=1;i<=3*n;i++) father[i]=i; for (int i=1;i<=m;i++) { int t,x,y; cin>>t>>x>>y; if (x>n||y>n) { ans++; continue; } if (t==1) { if (find(x+n)==find(y)||find(x+n*2)==find(y)) { ans++; continue; } uni(x,y);uni(x+n,y+n);uni(x+2*n,y+2*n); } if (t==2) { if (find(x)==find(y)||find(x+2*n)==find(y)) { ans++;continue; } uni(x,y+2*n);uni(x+2*n,y+n);uni(x+n,y); } } cout<<ans; return 0; }
T4 银河英雄传说
同样是并查集,不过这道题要记录一下舰队的长度,来进行“首尾相接”的操作。
#include<bits/stdc++.h> using namespace std; int f[30001],s[30001],b[30001]; int find(int o) { if(f[o]==o) return o; int k=f[o]; f[o]=find(f[o]); s[o]+=s[k]; b[o]=b[f[o]]; return f[o]; } int main() { int n; cin>>n; for(int i=1;i<=30000;i++) {f[i]=i;s[i]=0;b[i]=1;} for(int i=1;i<=n;i++) { char ch; int x,y,dx,dy; cin>>ch>>x>>y; if(ch=='M') { dx=find(x); dy=find(y); f[dx]=dy; s[dx]+=b[dy]; b[dx]+=b[dy]; b[dy]=b[dx]; } if(ch=='C') { dx=find(x); dy=find(y); if(dx!=dy){cout<<-1<<endl;continue;} cout<<abs(s[x]-s[y])-1<<endl; } } return 0; }
T5 星球大战
并查集+连通块。在进行完所有合并操作之后扫一下fa[],看如果有不同的数字出现ans++。最后ans即为答案。另外,这道题可能要利用逆向思维。
#include<bits/stdc++.h> using namespace std; int fa[400005],ans[400005],broken[400005],Broken[400005]; vector<int> v[400005]; int find(int x) { if (x==fa[x]) return x; return fa[x]=find(fa[x]); } inline bool quary(int x,int y) { return find(x)==find(y); } inline void merge(int x,int y) { fa[find(x)]=find(y); } int main() { int n,m,tot; cin>>n>>m; tot=n; for (int i=1;i<=n;i++) fa[i]=i; for (int i=1;i<=m;i++) { int x,y; cin>>x>>y; v[x].push_back(y); v[y].push_back(x); } int k;cin>>k; for (int i=1;i<=k;i++) { cin>>broken[i]; Broken[broken[i]]=1; } for (int i=0;i<n;i++) { if (!Broken[i]) for (int j=0;j<v[i].size();j++) if (!quary(i,v[i][j])&&!Broken[v[i][j]]) { merge(i,v[i][j]); tot--; } } tot-=k; ans[k]=tot; for (int i=k;i>=1;i--) { tot++; Broken[broken[i]]=0; for (int j=0;j<v[broken[i]].size();j++) if (!quary(broken[i],v[broken[i]][j])&&!Broken[v[broken[i]][j]]) merge(broken[i],v[broken[i]][j]),tot--; ans[i-1]=tot; } for (int i=0;i<=k;i++) cout<<ans[i]<<endl; return 0; }
后记:并查集类的题一般思维都比较巧妙,本身实现并不太难,想明白了发现其实也就那样。难的是几种算法综合在一起,需要一定的思维和代码能力。