并查集
怎么把
哈哈,直接连双向边就行了。查询的时候从一个点开始
但是这样,连接-查询是
我们给每个点设置一个老大,一开始所有人的老大都是自己。(牢大,想你了)
for(int i=1;i<=n;++i) f[i]=i;//f[i]表示i的老大是谁。
如果两个点要连边,那么就把其中一个人的老大设置成另一个人。
比如
如果两个人的老大是同一个人,那么说明这两个人是一伙的。
这个老大要找的是最终的老大,因为有可能长这样:
这时候
int find(int k)
{
if(f[k]==k) return k;//如果老大是自己,返回自己。
return find(f[k]);否则继续找老大的老大
}
2
3
4
5
但是这样有问题,比如先连了
本来
这时候可以这样理解,
以此类推,其实这场架是双方的最终老大在干架,所以应该把双方的最终老大连起来。
void merge(int x,int y)//连接x,y
{
if(find(x)==find(y)) return;//如果两个人其实是一伙的,跳过。
f[find(x)]=find(y);//两个人的最终老大合并了。
}
2
3
4
5
但是这样查询的复杂度还是
我们有办法!这个图的形态要比一般的图简单很多,所以可以考虑一些优化方法。
路径压缩
首先,我们判断某两个点是不是联通,只需要一个信息,就是点的最终老大是谁。
所以我们连这么长一条链是没有用的,不如在查询的时候做一点手脚:
我们将查询路径上遇到的点,全都直接去接到最终的老大下面。这样下次再查的时候直接
int find(int k)
{
if(f[k]==k) return k;
int t=find(f[k]);
return f[k]=t;
}
/*
更简单的写法
int find(int k){return f[k]==k?k:f[k]=find(f[k]);}
*/
2
3
4
5
6
7
8
9
10
复杂度分析:我也不太会
单纯的加上的路径压缩,单次查询复杂度上界是
按秩合并
(其实是一种启发式合并)
还有另一个办法:如果我们不想破坏这个链的形态,也就是我们还关心每个点的直接老大是谁,那么可以用按秩序合并。
在合并两个最终老大的时候,之前没有去管哪个接在哪个下面,这里其实可以优化。
设两个最终老大
不妨假设
如果
如果
所以我们每次把深度比较小的那个接到大的下面。
如果两个节点的最大深度不同,那么花费的最大时间是没变的(单次查询花费的最大时间就是深度,因为每次走一步。)
所以当且仅当两个节点的最大深度相同的时候,平均花费时间才会
然而这个
假设单个节点的深度是
int f[N],r[N];//r表示深度
void merge(int x,int y)
{
x=find(x),y=find(y);
if(x==y) return;
if(r[x]>r[y]) swap(x,y);
f[x]=y;
r[y]+=(r[x]==r[y]);
}
2
3
4
5
6
7
8
9
注意这里的想法类似启发式合并,但这里是按深度由小向大合并,因为在并查集中,复杂度是和深度有关的,而不是节点个数。
如果两个优化方法一起用,单次复杂度是
删除
若要删除,则将自己的牢大重新指向自己。
这要求的原本的机构不被破坏,即只能用按秩合并,不能用路径压缩。
扩展域并查集
给每个点一个反义节点,用来维护不同种类的关系问题。
通常用法是判断一个图是不是二分图(即图中所有节点的可以分为两部分,同一部分的节点之间互相没有边,或者说每一条边的两边节点都属于不同的部分,最多有两个部分)也可以用并查集:
假设有
如果两个点
这样就代表着,
假如之后连边是这样的:
那么
在试图把
带权并查集
并查集向上的边也可以携带一些信息:
有三类生物,
有两种关系:
给定关系,如果新给出的关系和已经有了的关系冲突,就是假话,问有多少句假话。
我们把三种关系分别标号为
如果
那么可以在并查集连边的同时,维护一个信息
那么比如
也就是两点之间的路径和对