并查集

并查集

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的反点链接,即

\[add(u+n,v);add(u,v+n) \]

然后就连接起来了,你可能怀疑他的正确性,但是后面会有说明

然后如果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)在两个并查集做合并的时候,权值也要做相应的更新,因为两个并查集的根节点不同

带权并查集没什么好解释的,放下一道例题来看看就明白了

P1196 [NOI2002] 银河英雄传说

#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;
}
posted @ 2022-08-03 21:17  Low_key_smile  阅读(24)  评论(0编辑  收藏  举报
//music