并查集简介
并查集
是一种常见的数据结构
主要用于解决"亲戚"
问题,如
x
x
x 的亲戚为
y
y
y,
y
y
y 的亲戚为
z
z
z,那么我们说
x
x
x 也是
z
z
z 的亲戚
主要思想
我们定义一个数组
fa[x]
表示 x x x 的父亲节点
我们定义一种函数find(x)
查询 x x x 所属路径的根节点
有什么用呢?
当
x
x
x 与
y
y
y 形成亲戚关系
是,我们令
y
y
y 为
x
x
x 的祖先
(即 find(x)
)的父亲
最终会有亲戚关系的会形成如下的一条链
所以
x
x
x,
y
y
y,
z
z
z,
f
f
f 互为亲戚
此时,
f
i
n
d
(
x
)
find(x)
find(x) 为
f
f
f
那么我们可以根据 find() 函数
是否相同判断
两个人是否为亲戚
还有一个东西叫路径压缩
如找
f
i
n
d
(
x
)
find(x)
find(x) 时,可以把
x
x
x 的父亲直接链接到祖先
如图,红线指向压缩后的父亲
路径压缩
后时间复杂度将会减少,不会一次一次慢慢找了
模板代码
题目链接
:P1551 亲戚
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
using namespace std;
int n,m,p,x,y,f[5005];
int find(int x){ //找x的祖先
if(f[x]==x) return x;
else return f[x]=find(f[x]); // 路径压缩
}
int main(){
scanf("%d%d%d",&n,&m,&p);
for(int i=1;i<=n;i++) f[i]=i; //初始化,i的父亲为i
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
f[find(x)]=find(y); //x的祖先的父亲链接到y的祖先
}
for(int i=1;i<=p;i++)
{
scanf("%d%d",&x,&y);
if(find(x)==find(y)) cout<<"Yes"<<endl; //如果祖先相同,那么肯定就是亲戚
else cout<<"No"<<endl;
}
return 0;
}
进阶题目
题目链接
:P1892 [BOI2003] 团伙
相对于基础题目,这道题多了一个敌人
关系
一个人不可以和自己的敌人组成一个团伙
但是
一个人的朋友的朋友是朋友
一个人的敌人的敌人是朋友
如下图,
2
2
2 的敌人为
1
1
1,
4
4
4 的敌人为
1
1
1,所以
2
2
2 和
4
4
4 为朋友
样例
样例输入
6
4
E 1 4
F 3 5
F 4 6
E 1 2
样例输出
3
样例解释
解决思路
我们可以发现,如果只有
朋友关系的图
那么敌人关系
和无关系
容易弄混
所以我们开两倍n
的数组,大于
x
+
n
x+n
x+n 的部分表示
x
x
x 的敌人
然后当
x
x
x 于
y
y
y 形成敌人关系
时,我们把
x
+
n
x+n
x+n 于
y
y
y 相连
f [ f i n d ( x ) ] = f i n d ( y + n ) ; f[ find(x)]=find(y+n); f[find(x)]=find(y+n);
f [ f i n d ( x + n ) ] = f i n d ( y ) ; f[find(x+n)]=find(y); f[find(x+n)]=find(y);
代码
#include<bits/stdc++.h>
#define ll long long
#define endl '\n'
using namespace std;
int n,m,x,y,f[2005],a[2005],ans;
char c;
int find(int x){ //并查集模板(找x的祖先)
if(f[x]==x) return x;
else return f[x]=find(f[x]); //路径压缩
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=2*n;i++) f[i]=i; //i的父亲暂时为i
for(int i=1;i<=m;i++)
{
cin>>c; scanf("%d%d",&x,&y);
if(c=='F') //如果x于y为朋友关系
f[find(x)]=find(y); //同模板代码
else
{
f[find(x)]=find(y+n); //x的祖先与y的敌人相连
f[find(x+n)]=find(y); //x的敌人的祖先于y的祖先相连
}
}
for(int i=1;i<=n;i++)
{
if(a[find(i)]==0) ans++; //如果i的祖先所属的链没有出现,那么结果+1
a[find(i)]=1; //i的祖先所属的链出现了,记录
}
printf("%d",ans);
return 0;
}