并查集——带权路径

一、概念引入

普通的并查集仅仅记录的是集合的关系,这个关系无非是同属一个集合或者是不在一个集合。

而带权并查集,不仅记录集合的关系,还记录着集合内元素的关系或者说是元素连接线的权值。

二、实现

1、求 v 数组

带权并查集有一个新的数组叫做 v[i] 

意义是:从当前节点到根节点有向距离(这里定义A到B的有向距离为dis时,B到A的有向距离为-dis)

而对于两个点对 (x,y) 的距离,如果他们不在一个集合里则肯定没有距离(显然),而他们在集合里的有向距离则为 v[y]-v[x] (相当于是y到根节点的距离根节点到x点的距离)。

举个例子:

蓝边表示当前要加入的边,我们先讨论合并两个不同集合的情况

显然,在合并之前, fa[]=\{1,1,3,3\}

显然, find(2) = 1 , find(4) = 3

不妨使 fa[find(2)] = find(4)    =>  f(1) = 3 

那么在两个集合合并之前,3所在的集合元素的 v数组中的值不用变(因为他们的根还是3)。

而对于 1所在集合,他们的根变成了3,而因为原集合的所有元素是指向1的,事实上我们只要改变1的v数组值就行(类似于并查集中合并祖先的思想(好吧就是一样的))。

好现在开始手推 v[1] 的值,其实 v[1] = -v[2] - w + v[4] 


带权路径主要是    依据向量之间的关系    求解

当 把 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的根节点,令 fa[fx]=fy 。则有: v[fx] = -v[x] - w + v[y] 

 

2、如何由根节点更新同集合中的点 —— 路径压缩,同时改变 v 中的值 

红色是每个点原来的v值,蓝色是修改后的v值


容易想到,根节点肯定是第一个修改v值的,我们记i点修改后的v值为 v'[i] 

记根节点为root,则首先改变的则是 v'[root] 

因为v数组以及v'数组的相对大小是不变的(因为这个集合中的path是不变的。说人话就是 v[i]-v[j] = v'[i] - v'[j],且i,j都在这个集合里)

因此我们可以从根节点开始,逐层改变节点的v值,因为根节点的v值肯定是0,因此上面的式子可以写成: v'[i]=v[i] + v'[root] 因此我们可以写成这个样子:

int find(int x)
{
    if (x == pre[x])
        return x;
    int root = find(pre[x]);    // 不断找根节点
    v[x] += v[pre[root]];       // 逐层累加
    return pre[x] =root;
}

猜你喜欢

转载自blog.csdn.net/JKdd123456/article/details/89110818