关于普通并查集
一般的并查集主要记录节点之间的链接关系,而没有其他的具体的信息,仅仅代表某个节点与其父节点之间存在联系,它多用来判断图的连通性,如下图所示,这是一个并查集,其中箭头表示父子关系,可以看到这些边没有记录其他的任何信息。
什么是带权并查集
而有的时候在这些边中添加一些额外的信息可以更好的处理需要解决的问题,在每条边中记录额外的信息的并查集就是带权并查集。
不过想必大家已经学过并查集的路径压缩了吧!
在上边这个并查集中,如果对节点C做Find操作,最终会得到A,但是查找的过程中会先经过B,再通过Find(B)得到A,这是一个值得优化的地方,如果直接让C链接到A不是更好吗,这样就可以省去中间的操作,如果C跟A直接相隔很多节点,这个优化就极大地提升了查找的效率,也就是希望得到这样的结果:
寻找根节点
将每个节点直接与其Find()操作最终得到的节点链接,就是所谓的路径压缩,这一操作可以直接在Find中完成,看下边的代码:
int get(int x)
{
return fa[x]==x?x:fa[x]=get(fa[x]);
}
与一般的并查集相比,它只是在find(parent[x])前边加了一步赋值操作,将在查找过程中遇到的所有的节点的父节点都设为最终得到的那个节点。
基于路径压缩,带权并查集就是:
可以看到它的每一条边都记录了每个节点到根节点的一个权值,这个权值该设为什么由具体的问题而定,一般都是两个节点之间的某一种相对的关系,但是考虑到权值就会有两个问题:
1.每个节点都记录的是与根节点之间的权值,那么在Find的路径压缩过程中,权值也应该做相应的更新,因为在路径压缩之前,每个节点都是与其父节点链接着,那个Value自然也是与其父节点之间的权值
2.在两个并查集做合并的时候,权值也要做相应的更新,因为两个并查集的根节点不同。
下面就来看在这两个过程中,如何更新权值:
路径压缩:
int get(int x)
{
if (x != fa[x])
{
int t = fa[x];
parent[x] = get(fa[x]);
value[x] += value[t];
}
return fa[x];
}
合并
合并:
int px = find(x);
int py = find(y);
if (px != py)
{
parent[px] = py;
value[px] = -value[x] + value[y] + s;
}
现在是已知x所在的并查集根节点为px,y所在的并查集根节点为py,如果有了x、y之间的关系,要将px并到py上,如果不考虑权值直接修改parent就行了,但是现在是带权并查集,必须得求出px与py这条边的权值是多少,很显然x到py两条路径的权值之和应该相同,就不难得出上面代码所表达的更新式。但是需要注意并不是每个问题都是这样更新的,有时候可能会做取模之类的操作。
推荐例题
HDU-3038-How Many Answers Are Wrong
HihoCoder-1515-分数调查
poj-1182-食物链