最开始接触并查集是在学Kruskal算法时,我将其分为init、Find和Union三个部分:
init初始化:
int f[MAX];
int rank[MAX];//秩(树的高度),详见Union,可省略
void init(int n)
{
for(int i=0;i<n;i++)
{
f[i]=i;
rank[i]=0;//可省
}
}
Find查找:(此处记录了三种我常用的写法,其中路径压缩是指:每次查找时,将查找路径上的每个节点都直接指向根节点)
// 最简洁版:
int Find(int x)
{
if(x==f[x])
return x;
else
return Find(f[x]);
}
// 路径压缩递归版:
int Find(int x)
{
if(x!=f[x])
f[x]=Find(f[x]);
else
return x;
}
// 路径压缩非递归版(高效):
int Find(int x)
{
int xx=x;
while(x!=f[x])//找根节点
x=f[x];
while(xx!=x)
{
int t=f[xx];
f[xx]=x;
xx=t;
}
return x;
}
Union合并:(记录两种写法,其中按秩合并是指:合并时将具有较小秩的树根指向具有较大秩的树根,换句话说,就是将较矮的树作为子树添加到较高的树中)
// 简洁版:
void Union(int u,int v)
{
int u0=Find(u),v0=Find(v);
if(u0!=v0)
f[v0]=u0;
}
// 按秩合并(高效):
void Union(int u,int v)
{
int u0=Find(u),v0=Find(v);
if(u0==v0)
return;
if(rank[u0]>rank[v0])
f[v0]=u0;
else
{
f[u0]=v0;
if(rank[u0]==rank[v0])
rank[v0]++;
}
}
接下来是带权并查集(重点),记录下来我的一些理解和做题套路。
首先贴大佬博客Orz:
模板代码及详解:https://blog.csdn.net/Chris_zzj/article/details/52227656
典型例题及代码:https://blog.csdn.net/tribleave/article/details/72878239
我对带权并查集的理解:
就是并查集多了个val数组,表示i和f[i]之间的关系(可以是距离、状态等),Find函数和Union函数要对应改些内容。
我的做题思路:
1.画数轴;2.写Find函数;3.写Union函数(我通常直接写在main中)
主要记录两道题:
1.POJ2492 A Bug's Life(种类并查集):
思路中的2、3步详解如下:
这两部分就应该是最难搞的了,然后贴AC代码:
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAX=2005;
int n,m;
int f[MAX];
int val[MAX];//0:与f[i]同类,1:与f[i]不同类
int Find(int x)
{
if(f[x]==x)
return x;
int tmp=f[x];
f[x]=Find(f[x]);
val[x]=(val[x]+val[tmp])%2;
return f[x];
}
int main()
{
int t;
scanf("%d",&t);
for(int tt=1;tt<=t;tt++)
{
scanf("%d%d",&n,&m);
for(int i=0;i<=n;i++)
f[i]=i,val[i]=0;
int a,b;
bool sign=true;
for(int i=0;i<m;i++)
{
scanf("%d%d",&a,&b);
if(sign)
{
int u=Find(a);
int v=Find(b);
if(u==v)
{
if(val[a]==val[b])
sign=false;
}
else
{
f[v]=u;
val[v]=!(val[a]^val[b]);
}
}
}
printf("Scenario #%d:\n",tt);
if(sign)
printf("No suspicious bugs found!\n\n");
else
printf("Suspicious bugs found!\n\n");
}
return 0;
}
还有一道类似的题是POJ1182 食物链,那道题更麻烦,分为3类,但套路也差不多。
2.HDU3038 How Many Answers Are Wrong (区间统计并查集):
这道题同样画数轴,写出Find函数和Union函数,更新val[x]和val[fb]。思路就不具体写了,直接贴AC代码吧:
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAX=200005;
int n,m;
int a[MAX];
int f[MAX];
int val[MAX];//保存的是f[i]到i的和,可能为负
int Find(int x)
{
if(f[x]==x)
return x;
int tmp=f[x];
f[x]=Find(tmp);
val[x]+=val[tmp];//修改val值
return f[x];
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
for(int i=0;i<=n;i++)
f[i]=i,val[i]=0;
int a,b,s;
int ans=0;
for(int i=0;i<m;i++)
{
scanf("%d%d%d",&a,&b,&s);
a--;//注意!!对区间[l,r]进行记录时,实际上是对(l-1,r]操作,即l=l-1(即势差是在l-1和r之间)
int fa=Find(a);
int fb=Find(b);
if(fa!=fb)
{
f[fb]=fa;
val[fb]=val[a]-val[b]+s;
/*s:(a,b]
val[a]:[fa,a]
val[b]:[fb,b]
[fa,a]+(a,b]=[fa,b];
[fa,b]-[fb,b]=[fa,fb];
又fa=f[fb],故val[fb]=[f[fb],fb]=val[a]+w-val[b]*/
}
else
if(val[b]-val[a]!=s)
ans++;
/*s:(a,b]
val[a]:[fa,a]
val[b]:[fb,b]
[ff,b]-[ff,a]=[a,b]*/
//此题画数轴更容易理解
}
printf("%d\n",ans);
}
return 0;
}