并查集的特殊运用

我之前对于这种题目一直都是选择自己想的“3染色DFS”,没想到还可以用并查集来做,长见识了。

P2661 [NOIP2015 提高组] 信息传递

题目大意:

每个点只有一条出边,问最小环是多大。

思路:

  1. “自己的发明”
  2. 并查集

1. “3染色DFS”

一直是用这个做,但现在会不一样的了!!!

2. 并查集

上来可能会想不明白,这个建立的是单向边,为什么可以用并查集呢?

答案就是这类问题很特殊:每个点只有一个出边

这就导致了一旦形成环的话,就一定是个基环树

并且,在形成环之前,这就一定是一棵内向树

这就保证了如果直接用并查集维护的话,会有这两个性质:

  1. 只有在形成环的时候会出现同一连通块相连的情况。
  2. 在形成环之后整个连通块就形成闭环,再也不会产生操作。

这是因为整个连通块(内向树)里面,只有根节点没有出边,所以在连边操作一定是从根节点出发的,如果连到自己这个连通块的点的话就是环,如果是别的连通块,就是连通块合并。而且合并之后整个连通块还一定是一个内向树。

如果形成环,那么这个连通块的所有点就都有出边了,所以再也不会有操作了。

所以当出现环的时候,就可以相当于直接将整个连通块的点删除。所以只会留下内向树。

正因为无论什么时候这个图都一定是内向树森林,所以用并查集维护是没有问题的。

可以配合下图理解(意会):

  1. 形成环:

  1. 连向别的连通块。

既然我们可以用并查集维护的正确性没有问题,那只有最后一个问题:

怎么记录环的长度?

答案很简单,我们可以记录每个节点到根节点的距离 \(len_i\),在形成环的时候就可以轻松统计环的长度(相连两点的 len 相加 +1 )。

然后我们在维护的时候只需要在路径压缩的时候确保了len的正确性就好了(具体看代码)。

Code:

#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int rt=0;	char g=getchar();
	while(g<'0'||g>'9')	g=getchar();
	while(g>='0'&&g<='9')	rt=(rt<<3)+(rt<<1)+g-'0',g=getchar();
	return rt;
}
int n,ans;
int a[200005];
int len[200005];
inline int BOSS(int A)
{
	if(a[A]!=A)
	{
		int lst=a[A];
		a[A]=BOSS(a[A]);
		len[A]+=len[lst];
	}
	return a[A];
}
inline void check(int A,int B)
{
	int AA=BOSS(A),BB=BOSS(B);
	if(AA==BB)	ans=min(ans,len[A]+len[B]+1);
	else
	{
		len[AA]=len[B]+1;
		a[AA]=BB;
	}
}
int main()
{
	n=ans=read();
	for(int i=1;i<=n;i++)	a[i]=i;
	for(int i=1,to;i<=n;i++)	check(i,read());
	printf("%d",ans);
	return 0;
}
posted @ 2024-07-11 15:05  YT0104  阅读(5)  评论(0编辑  收藏  举报