【学习笔记】基础算法——并查集
并查集
二话不说上模板!
这不大水吗,都毫无难度。只要写好一个 FF 函数一切就完事儿了。
int FF(int u){return (u==fa[u]?u:fa[u]=FF(fa[u]));}
冷知识:大家一般都取做 Find、find 之类的,我这函数叫 FF 这名儿是什么意思呢?其实原本我也是叫 Find 的,但是我觉得这名讲得不够清楚,就又用了一段时间的 Find_Father,可是太长了,多难敲啊,因此我就取了 Find 和 Father 的首字母,所以这函数就变成 FF 了……笑啥,又好敲,又好记,还有详细含义,还少见,多棒啊!(
代码就不贴了,相信不到 \(20\) 行就能轻松搞定吧,我有啥贴代码的必要呢。
拓展域并查集
说白了可以理解为那啥的“敌人的敌人是朋友”。
哎这样讲多不明白啊,搬仨题出来吧。
例题——关押罪犯
很不错的题。
首先肯定贪心嘛,冲突大的万不可放一块儿。
那么每个罪犯就抽象为一个点 \(u\),\(u\) 的反状态是 \(u+n\)。
假若我们不想让 \(u\) 和 \(v\) 关一块儿,那就让它们各自与对方的反状态关一块儿去,也就是说连俩边,一个是 \((u,v+n)\),另一个是 \((u+n,v)\)。
什么时候不得不关一块儿呢?当且仅当不想让 \(u\) 和 \(v\) 关一块儿,已经连完反状态边以后,存在某个点 \(x\) 满足 \(x\) 与 \(x+n\) 处于一个集合里了。那就不行了!那么这一次的 \(u\) 和 \(v\) 就不得不关一块儿了。这个时候输出答案并 return 0 就是啦!
那么本题就结束了。
还是贴一下代码吧,你说呢?
#include<bits/stdc++.h>
using namespace std;
const int N = 2e4+5 , M = 1e5+5;
struct line{int u,v,w;}ln[M];int n,m,fa[2*N];
bool cmp(line l1,line l2){return l1.w>l2.w;}
int FF(int u){return (fa[u]==u?u:fa[u]=FF(fa[u]));}
void Merge(int u,int v){if(FF(u)!=FF(v))fa[FF(v)]=FF(u);return;}
int main(){
cin>>n>>m;
for(int i=1;i<=2*n;i++)fa[i]=i;
for(int i=1;i<=m;i++)cin>>ln[i].u>>ln[i].v>>ln[i].w;
sort(ln+1,ln+m+1,cmp);
for(int i=1;i<=m;i++){
Merge(ln[i].u+n,ln[i].v),Merge(ln[i].u,ln[i].v+n);
if(FF(ln[i].u)==FF(ln[i].u+n)||FF(ln[i].v)==FF(ln[i].v+n)){cout<<ln[i].w<<"\n";return 0;}
}
cout<<"0\n";return 0;
}
习题——The Door Problem
由于用洛谷可以比较方便的看到翻译所以给的是洛谷的链接。当然啦,CF 链接。
这跟上面那个题很像吧!
只是说对于开着的,就合并 \((u,v)\) 以及 \((u+m,v+m)\),而对于关着的,才合并 \((u,v+m)\) 和 \((u+m,v)\)。
同样的只要存在某个 \(x\) 满足 \(x\) 与 \(x+m\) 待一个集合里了那么就无解啦。否则有解!
贴代码!
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+5;
int n,m,p[2][N],cnt[N],fa[N];bool a[N],flag;
int FF(int u){return (fa[u]==u?u:fa[u]=FF(fa[u]));}
void Merge(int u,int v){fa[FF(v)]=FF(u);return;}
int main(){
cin>>n>>m;flag=1;
for(int i=1;i<=n;i++)cin>>a[i];
memset(cnt,-1,sizeof(cnt));
for(int i=1;i<=m;i++){
int ct;cin>>ct;
while(ct--){int x;cin>>x;p[++cnt[x]][x]=i;}
}
for(int i=1;i<=2*m;i++)fa[i]=i;
for(int i=1;i<=n;i++)
if(!a[i])Merge(p[0][i],p[1][i]+m),Merge(p[0][i]+m,p[1][i]);
else Merge(p[0][i],p[1][i]),Merge(p[0][i]+m,p[1][i]+m);
for(int i=1;i<=m;i++)if(FF(i)==FF(i+m))flag=0;
if(flag)cout<<"YES\n";else cout<<"NO\n";
return 0;
}
作业
这个是三倍的,也就是说存在 \(u\)、\(u+n\) 以及 \(u+2n\)。因为有三种动物循环吃,所以这是三倍的扩展域并查集。
总结
并查集!
并查集本身不难但套上乱七八糟的一大堆东西以后就不简单啦。各种各样的方法,各种各样的情况下,包括各种变种。很难的呐!
各种运用,各种创新。不断挖掘!

浙公网安备 33010602011771号