Contents:
一.引入
从图的角度理解:
并查集实际上是一颗由若干个树构成的森林,而如果还需要储存边的权值,则需要额外用一个数组d记录每个结点和其父节点的权值
从集合的角度理解:
并查集储存的信息是某点属于哪个集合,而带权并查集还能记录某点和其父结点的“关系”
这个思路其实很简单,但是路径压缩函数和集合合并函数都需要改写:
①因为路径压缩会改变点与点的连接关系,路径压缩后,点的父节点变为了根节点,所以需要更新d数组
②集合的代表元(根节点)是没有父结点的,所以d数组中也没有值。但是合并集合后其中一个根节点变为了另一个集合根节点的子节点,所以需要赋值
二.查询函数与集合合并函数(伪代码)
查询函数:
int root_find(int x){
if(x==pre[x]) return x; //如果x==pre[x],说明x是根节点,根节点也不需要更新权值,直接返回x就行
int root=root_find(pre[x]); //递归寻找根节点
更新d[x]值;
return pre[x]=root; //更新父结点(路径压缩)
}
说明一下递归函数的逻辑:
它会先执行root_find()函数直至找到根节点,再回来执行当前函数中剩余没执行完的部分
也就是在你更新d[x]值时,d[pre[x]]是已经更新完了的
集合合并函数:
根节点是不保存权值的,因为根节点没有父节点,而在树的合并时,其中一个树的根节点会变为另一个树的子结点,所以也需要为其赋上权值
void join(int x,int y){
int rx=root_find(x);
int ry=root_find(y);
if(rx!=ry){
pre[rx]=ry;
更新d[rx]值;
}
}
三.权值如何更新
上面的函数都是用伪代码写的,因为更新权值的规则要视题目而定
这个权值其实描述的是一种“关系”
以POJ-1182这道很经典的食物链为例,题目规定三种物种:A吃B,B吃C,C吃A
可以用0表示和父结点同类,1表示吃父结点,2表示被父结点吃
则在更新权值时,可以这么写:
int root_find(int x){
if(x==pre[x]) return x;
int root=root_find(pre[x]);
rela[x]+=rela[pre[x]],rela[x]%=3;
return pre[x]=root;
}
前面说过,因为递归函数的特性,所以: ①rela[pre[x]]必然已经被更新为pre[x]到根节点的权值了
②而rela[x]存放的是x到pre[x]的权值
根据这两段已知的关系(权值)我们可以推断出x与根节点的关系
因为这道题三者循环的克制关系,可以使用模运算进行操作
集合合并会更复杂些,是由三对已知的关系推第四个已知关系:
即已知点x和rx的关系,点y和ry的关系,点x和点y的关系,推导点rx和点ry的关系
void join(int x,int y,int r){
int rx=root_find(x);
int ry=root_find(y);
if(rx!=ry){
pre[rx]=ry;
rela[rx]=(r+rela[y]-rela[x]+3)%3;
}
}
具体的权值更新方式还是得看题目,但是原理大致就是如此了