算法学习笔记(3):带权并查集的实现及其应用

带权并查集

普通的并查集只能维护每个节点所在集合的编号,带权并查集则可以维护集合内任意一点到所在集合根的距离。

简单来说,带权并查集支持如下两种操作:

  • 在点 uv 之间连一条边权为 w 的边(u,v 在不同的连通块内)。

  • 查询点 u 与该连通块的根(即并查集的根)的距离。

find

int find(int x)
{
if (f[x] == x)
        return x;
    int fa = find(f[x]);
    dis[x] += dis[f[x]];
    return f[x] = fa;
}

f[x]:点 x 的父亲节点。

dis[x]:点 x 到其 父亲节点(注意不是根节点)的距离。

请一定注意 dis 数组的定义,如果我们要询问 x 到根路径的权值和,只需要先调用一遍 find 函数(由于使用了路径压缩,此时 x 的父亲就为并查集的根)即可。

merge

void merge(int x, int y, int c)
{
int fx = find(x), fy = find(y);
    if (fx == fy)
        return;
    f[fx] = fy, dis[fx] = c + dis[y] - dis[x];
}

merge(x, y, c):在点 x 和点 y 直接连一条权值为 c 的边,同时将 x 所在的连通块合并到 y 上。

这里是带权并查集比较难理解的一部分,配合图片食用更加。

这是初始状态,我们先调用了一次 find(x)find(y)xy 所在集合的根以及其到根的距离都已经得到。

我们的任务是在 xy 之间连边,但直接修改 f[x]f[y] 都会破坏并查集的结构,因此只能合并 fxfy

明确一点,根据我的写法,最终集合的根应该为 fy。所以 x 到根路径的权值和应为 c+disy(为 xy 这条边的权值 + y 到根路径的权值和)。

所以只要在 fxfy 之间连一条边权为 c+disydisx 的边,x 到根的路径和依旧是 disx+c+disydisx=c+disy。对应的就是将 dis[fx] 的值修改为 c+disydisx


例题:给定 n 个变量 a1anm 个约束,每个约束形如 (x,y,c) 表示 axay=c,判断是否存在可行解。

解法:把每个约束拆成 axaycaxayc,就可以用差分约束来解,但可能会被卡到 O(nm)。如果使用带权并查集来做,则可以在接近线性的时间内解决。

对于每个约束 (x,y,c),分两种情况考虑:

  • xy 不在同一连通块内,从 xy 连一条权为 c 的边。

  • xy 在同一连通块内,只要判断 (x 到根路径权值和) 减去 (y 到根路径权值和) 是否为 c 即可。


例题:⌈NOI2002⌋ 银河英雄传说

解法:对于 M i j,从 (i 所在集合的根) 向 (j 所在集合的根) 连一条权为 (i 所在连通块大小) 的边;对于 C i j,输出 | (i 到根的权值和) - (j 到根的权值和) | 即可。


例题:⌈NOI2001⌋ 食物链

解法:考虑到 A 捕食 BB 捕食 CC 捕食 A,这种循环关系,我们通过在带权并查集中将权值对 3 取模,即可快速判断两者关系。

如果 ij 不在同一集合:

  • i,j 是同类,则从 ij 连一条权为 0 的边。

  • i 捕食 j,从 ij 连一条权为 1 的边。

如果 ij 在同一集合:

  • i,j 是同类,则 (i 到根权值和) 不等于 (j 到根权值和) 则为假话。

  • i 捕食 j,则 (i 到根权值和) - (j 到根权值和) 不为 1 则为假话。

posted @   CodingShark  阅读(767)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示