普通并查集
我们在处理问题的时候,可能会遇到一些需要维护每个元素所在的集合的问题,而并查集却恰好完美解决了这个问题。
对于普通的并查集,他支持的操作比较少,只有合并和查询,合并是指把两个集合合并成一个,而查询是询问两个元素是否在同一集合内;对于这两种操作,我们可以用一个数组
inline void fid(int x){return f[x]==x?x:fid(f[x]);}
//如果当前的父节点是自己就返回自己,反之返回代表元素的集合所在的代表元素编号依次直到f[x]==x;
当然我们在使用的时候发现这个 fid 如果一直往后递归会很费时间,所以我们用可以用以下的代码来优化一下:
inline void fid(int x){return f[x]==x?x:f[x]=fid(f[x]);}
这种优化也叫路径压缩,我个人的理解就是加了个记忆化。
那合并呢,咋搞?
假设我们需要把
法一:直接暴力把所有的点遍历一遍,把所有的属于
法二:只修改
很显然第一个会 T 飞,第二个复杂度很不错但是如何来实现呢?
我们之前记录的
但是当
所以我们在进行修改的时候改的其实并不是把
P3367 【模板】并查集
code:
#include
using namespace std;
int n,m,f[100010];
inline int fid(int x)
{
if(f[x]==x)return x;
else return f[x]=fid(f[x]);
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
f[i]=i;
for(int i=1;i<=m;i++)
{
int op,x,y;
cin>>op>>x>>y;
if(op==1)
{
int xx=fid(x);
int yy=fid(y);
f[xx]=f[yy];
}
else if(op==2)
{
int xx=fid(x);
int yy=fid(y);
if(xx==yy)
cout<<"Y"<else cout<<"N"<return 0;
}
扩展域并查集
我也不知道是不是叫这个名字,但是是解决
结合一道题目来讲一下具体怎么用:
P1892 [BOI2003]团伙
一个人的朋友的朋友是朋友
一个人的敌人的敌人是朋友
把一个人
最后你就会发现,和
如果
如果
那么
code:
#include
using namespace std;
int n,m,f[15000],bd[15000],ans=0;
int fid(int x)
{
if(f[x]==x)return f[x];
else return f[x]=fid(f[x]);
}
void cun(int x,int y)
{
int xx=fid(x);
int yy=fid(y);
if(xx!=yy)f[yy]=xx;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n*2;i++)
f[i]=i;
for(int i=1;i<=m;i++)
{
int b,c;
char a;
cin>>a>>b>>c;
if(a=='F')
cun(b,c);
if(a=='E')
{
cun(b+n,c);
cun(c+n,b);
}
}
for(int i=1;i<=n;i++)
{
int t=fid(i);
if(bd[t]==0)
bd[t]=1,ans++;
}
cout<return 0;
}
P2024 [NOI2001] 食物链
这个题和上面的有什么区别?
上一个题目里一个人只需要劈两半,因为 A 可以和 B 为敌,B 和 A 为敌。
但是这个不可以,A 可以吃 B,但这样 B 不能吃 A。
所以我们一个人劈三半!A 吃 B,B 吃 C,C 吃 A!
在合并的时候,我们就正常每一个集合都合并,对于 A 吃 B 之类的操作,依次标记即可。
code:
#include
#define N 10001000
using namespace std;
int n,k,f[N],ans,a,b,c;
inline int fid(int x){return f[x]==x?x:f[x]=fid(f[x]);}
signed main()
{
cin>>n>>k;
for(int i=1;i<=3*n;i++)f[i]=i;
for(int i=1;i<=k;i++)
{
cin>>a>>b>>c;
if(b>n||c>n){ans++;continue;}
if(a==1)
{
if(fid(b+n)==fid(c)||fid(c+n)==fid(b)){ans++;continue;}
f[fid(b)]=fid(c);
f[fid(b+n)]=fid(c+n);
f[fid(b+n+n)]=fid(c+n+n);
}
if(a==2)
{
if(fid(b)==fid(c)||fid(b)==fid(c+n)){ans++;continue;}
f[fid(b+n)]=fid(c);
f[fid(b+n+n)]=fid(c+n);
f[fid(b)]=fid(c+n+n);
}
}
cout<return 0;
}
加权并查集
我记得这东西还叫带权并查集。
这个东西和普通的并查集有什么区别嘞?
他可以查询两个点之间的距离。
然后就没有别的了。。。。
我也不是很明白这个东西是怎么实现的,但是可以讲一下下面的这个题目(以后一定来填坑)
P1196 [NOI2002] 银河英雄传说
这个题目的询问不太一样:询问两个战舰之间的战舰数量。
我们开一个数组
我们和普通的并查集相比,其实只有两点不同:
一是我们的 fid 函数,在路径亚索压缩的时候顺便把
inline int fid(int x)
{
if(fa[x]==x)return x;//如果要是相等直接返回
int ff=fid(fa[x]);//更新后的代表元素编号
f[x]+=f[fa[x]];//累加求和
return fa[x]=ff;//返回新的代表元素编号
}
第二就是合并操作的改变,同样我们除了改变
f[xx]+=num[yy];//把xx一列接到yy一列的后面,所以直接加上num[yy]
fa[xx]=yy;//更新fa
num[yy]+=num[xx];//累加战舰数量
num[xx]=0;//清零
其实我觉得带权并查集都可以把集合给相成拍一列,因为这样的话能比较好理解和实现。
code:
#include
#define int long long
#define N 1000100
using namespace std;
int n,x,y,num[N],f[N],fa[N];
inline int fid(int x)
{
if(fa[x]==x)return x;//如果要是相等直接返回
int ff=fid(fa[x]);//更新后的代表元素编号
f[x]+=f[fa[x]];//累加求和
return fa[x]=ff;//返回新的代表元素编号
}
signed main()
{
for(int i=1;i<=30000;i++)
fa[i]=i,f[i]=0,num[i]=1;
cin>>n;
while(n--)
{
char c;
cin>>c>>x>>y;
int xx=fid(x);
int yy=fid(y);
if(c=='M')
{
f[xx]+=num[yy];//把xx一列接到yy一列的后面,所以直接加上num[yy]
fa[xx]=yy;//更新fa
num[yy]+=num[xx];//累加战舰数量
num[xx]=0;//清零
}
else
{
if(xx!=yy)cout<<"-1"<else cout<<abs(f[x]-f[y])-1<return 0;
}
可持久化并查集
之前的并查集都弱爆了,这个才是最强并查集。
前置知识:主席树,并查集
md没看懂,先咕了