并查集


声明:与学校集训内容无关。

并查集是一种树形结构,基本的应用就是判断两个元素是否在同一个集合内,也可以将两个元素所在的集合合并。

举个奇怪的例子。

原理&代码实现+优化

假设这里有一些P主,他们有不同的口味(派别),有摇滚,重金属,古典等等等。

现在我们假设有很多很多种口味,而我们设同一种口味的P主都是同事,会给出两个P主是同事关系,或询问两个P主是不是同事。

现在有一些P主,而我们假设初始时大家都是自成一派。

而我们将每个P主所在门派都选一个元老级的P主出来,i的直接元老是fa[i]。

所以刚开始元老都是自己:

for(int i=1;i<=n;++i){
        fa[i]=i;
    }

这就是并查集的初始化了。

现在我们将八王子P和滚苹果P合并入同一个派别,当然,滚苹果是元老。

于是我们把fa[八王子P]=滚苹果P。

我们再将雄之助和八王子P合并到同一个派别,但是,雄之助的元老还是八王子P吗?显然不是,而是这个门派的元老,八王子P的元老,滚苹果P。

所以我们赋值时可以这么操作:

//把八王子和雄之助合并一下
x=fa[雄之助],y=fa[八王子];
fa[x]=y;

接着我们合并一下雄之助和chinozo。

但是雄之助的直接元老是八王子,但是真正的元老是滚苹果啊!

所以有了找真正的元老的函数find。

int find(int x){
 return x==fa[x]?x:(find(fa[x]));
}

那么合并也可以进化一下了。

void merge(int x,int y){
  x=find(x),y=find(y);
  if(x!=y)fa[x]=y;
  return ;
}

所以,我们就有了一个完整的P主管理并查集。

那我们如何优化?

学会了两个,一个路径压缩,一个按轶合并。

如果一起用的话路径压缩会影响轶的准确性,所以不要一起用。

实测,写luogu上面的模版,路径压缩比按轶合并快了13ms。

·路径压缩

当趋向于一条链的时候,就像我那个一样,八王子到滚苹果,雄之助到八王子,chinozo到雄之助,那如果我再做关于chinozo的操作,要走很长。

于是我们试图让路变得更短,从chinozo直接到滚苹果。

(所以我这个就直接变成菊花图了是吧)

int find(int x){
 return x==fa[x]?x:(fa[x]=find(fa[x]));
}

·按轶合并

如果两个帮派,一个有114层,另一个有514层,我们显然把114层的那个往514那个上面并。

所以我们用一个Rank[i]表示以i为根节点的门派或者子树的深度。

而两个Rank[x]和Rank[y]不相同的时候,合并后对Rank值无影响,但俩Rank相同时,其中一个Rank要++。

void merge(int x,int y){
  x=find(x),y=find(y);
  if(x==y)return ;
  if(Rank[x]<=Rank[y])fa[x]=y;
  else fa[y]=x;
  if(Rank[x]==Rank[y])Rank[y]++;
  return ;
}

于是就有了高级的优化。

应用

1.判环

如P2661,想知道游戏会不会结束,看会不会有环,若有环,就会满足有一个i,使得i==find(t[i]),此时输出环的大小即可。

这就是一个带权并查集了。

2.种类并查集

我还没学会捏。

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/289523.html

(0)
上一篇 2022年9月14日
下一篇 2022年9月14日

相关推荐

发表回复

登录后才能评论