并查集
并查集
upd 2022.5.10
1.最简单的部分
1.首先是并查集的搜索
int find(int x)
{
if(f[x]==x) return x;
return f[x]=find(f[x]);
}
这个就是一直搜,直到搜到他的祖先是自己就停下,然后返回,这时返回的就是这个点的最大的祖先了,这就是路径压缩,是为了保证并查集不会退化成线性的
2.然后是并查集的添加
void add(int x,int y)
{
int a=find(x);
int b=find(y);
if(a!=b)
f[a]=b;
}
这个就是找到x和y的最大的祖先,看看两个祖先是不是相交,如果不相交那就并到一起
3.最后就是初始化
刚学经常忘,常错
for(int i=1;i<=n;i++)
f[i]=i;
n个点,默认每个点的祖先就是自己,然后在后面再慢慢添加进去别的父子关系,注意扩展域并查集需要初始化n*2,或者更多,看需求选择
2.各种各样的并查集
1.扩展域并查集:
扩展域并查集就是,拓展域并查集解决了一种多个有相互关系的并查集,放在一起考虑的问题。一般的并查集应用一般就是判断在不在一个集合,拓展域并查集讲的是多个集合,之间有相互关系一般为相互排斥关系,判断是否在一个集合等。
简单来说就是解决:敌人的敌人是我的朋友,朋友的朋友是我的敌人这类问题的(其实是我不知道还能解决什么)
来看看简单易懂的图片瞎搞的图
1,2,3是三个节点,1',2',3'是三个节点分别+n后的节点,就是可以看作是一个,中转点
当1和2是敌人的时候就把1和2的反点连接,2和1的反点链接,即
然后就连接起来了,你可能怀疑他的正确性,但是后面会有说明
然后如果2和3是敌人,按照之前的约定,要连接2和3',3和2';连接之后就是下图的样子,可以看到,反点已经都被连接好了
然后就来看,是不是符合:敌人的敌人是我的朋友,朋友的朋友是我的敌人,这个观点,把连接的线都圈起来,就会发现1,3连接在一起了,说明是正确的
扩展域并查集的小小例题P1892 [BOI2003]团伙
代码可以参考以下(我写的狠拉,见谅)
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,m,Ans,f[N],vis[N];
int Find(int x)
{
if(f[x]==x) return x;
return f[x]=Find(f[x]);
}
void add(int a,int b)
{
int x=Find(a);
int y=Find(b);
if(x!=y) f[x]=y;
}
signed main()
{
cin>>n>>m;
char ch;
int u,v;
for(int i=1;i<=n*2;i++) f[i] = i ;
for(int i=1;i<=m;i++)
{
cin>>ch>>u>>v;
if(ch=='F')
{
add(u,v);
}
else
{
add(u+n,v);
add(u,v+n);
}
}
Ans=0;
for(int i=1;i<=n;i++)
{
int t=Find(i);
if(!vis[t])
{
vis[t]=1;
Ans++;
}
}
cout<<Ans;
return 0;
}
有能力的可以去做一下食物链这道题,虽然是蓝题,但是是要会了上面那道题,你就能轻松A掉它
2.带权并查集
普通的并查集仅仅记录的是集合的关系,这个关系无非是同属一个集合或者是不在一个集合
带权并查集不仅记录集合的关系,还记录着集合内元素的关系或者说是集合内元素连接线的权值
普通并查集本质是不带权值的图,而带权并查集则是带权的图
考虑到权值就会有以下问题:
(1)每个节点都记录的是与根节点之间的权值,那么在Find的路径压缩过程中,权值也应该做相应的更新,因为在路径压缩之前,每个节点都是与其父节点链接着那个Value自然也是与其父节点之间的权值
(2)在两个并查集做合并的时候,权值也要做相应的更新,因为两个并查集的根节点不同
带权并查集没什么好解释的,放下一道例题来看看就明白了
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,m,Ans,f[N],sum[N],tot[N];
int Find(int x)
{
if(f[x]==x) return x;//如果说父亲就是自己那就直接返回
int t1=f[x],t2=Find(f[x]);//然后t1找到现在的x的父亲,t2找到x的祖先
f[x]=t2;//然后直接连接到x的祖先t2
tot[x] +=tot[t1];//他现在的长度就要加上他的爸爸的长度
return t2; //然后就返回他的祖先
}
// tot是存储这个点到队头的速度
// sum是存储这个点军队的长度
void add(int u,int v)
{
int a=Find(u);
int b=Find(v);//先找到两个节点的祖先
f[b]=a;//然后直接放入
tot[b]=sum[a];//因为事吧b合并在a中,所以tot[b]要加上suma
sum[a]+=sum[b];//然后更新suma
sum[b]=0; //更新完之后sumb就没有存在的价值了
}
signed main()
{
cin>>n;
for(int i=1;i<=N;i++)
{
sum[i]=1; //sum表示初始每个点都是1
f[i]=i; //初始化一下父亲
}
while(n--)
{
char ch;
int u,v;
cin>>ch>>u>>v;
if(ch=='M')
add(u,v);
else
{
if(Find(u)!=Find(v))
{
cout<<-1<<endl;//按照要求寻找即可,如果说找不到,那就直接输出-1,否则输出tot
}
else
{
cout<<abs(tot[u]-tot[v])-1<<endl;
}
}
}
return 0;
}