一、概念引入
普通的并查集仅仅记录的是集合的关系,这个关系无非是同属一个集合或者是不在一个集合。
而带权并查集,不仅记录集合的关系,还记录着集合内元素的关系或者说是元素连接线的权值。
二、实现
1、求 v 数组
带权并查集有一个新的数组叫做
意义是:从当前节点到根节点的有向距离(这里定义A到B的有向距离为dis时,B到A的有向距离为-dis)
而对于两个点对 的距离,如果他们不在一个集合里则肯定没有距离(显然),而他们在集合里的有向距离则为 (相当于是y到根节点的距离与根节点到x点的距离之和)。
举个例子:
蓝边表示当前要加入的边,我们先讨论合并两个不同集合的情况
显然,在合并之前,
显然, ,
不妨使 fa[find(2)] = find(4) => f(1) = 3
那么在两个集合合并之前,3所在的集合元素的 v数组中的值不用变(因为他们的根还是3)。
而对于 1所在集合,他们的根变成了3,而因为原集合的所有元素是指向1的,事实上我们只要改变1的v数组值就行(类似于并查集中合并祖先的思想(好吧就是一样的))。
好现在开始手推 的值,其实
带权路径主要是 依据向量之间的关系 求解
当 把 1 连接到 3 的时候,即 pre[1] = 3
那么 v[1] = -30 + (-20) + 10 因为 v 代表的是 这一个点到根节点的距离
或者是
v[1] = 30 + 20 - 10 ( 代表的是 根节点到这一点的距离 ,和上边不同的是 A-> B是dis , B-> A 是 -dis )
为了更加公式化一点,我们设 y 到 x 的一条边距离为w。
fx,fy分别为x,y的根节点,令 。则有:
2、如何由根节点更新同集合中的点 —— 路径压缩,同时改变 v 中的值
红色是每个点原来的v值,蓝色是修改后的v值
容易想到,根节点肯定是第一个修改v值的,我们记i点修改后的v值为
记根节点为root,则首先改变的则是
因为v数组以及v'数组的相对大小是不变的(因为这个集合中的path是不变的。说人话就是 ,且i,j都在这个集合里)
因此我们可以从根节点开始,逐层改变节点的v值,因为根节点的v值肯定是0,因此上面的式子可以写成: 因此我们可以写成这个样子:
int find(int x)
{
if (x == pre[x])
return x;
int root = find(pre[x]); // 不断找根节点
v[x] += v[pre[root]]; // 逐层累加
return pre[x] =root;
}