带权并查集

在这里插入图片描述

学习的最大理由是想摆脱平庸,早一天就多一份人生的精彩;迟一天就多一天平庸的困扰。

学习日记

目录

学习日记

一、并查集

 1、初始化

 2、查找

3、路径压缩 

 4、合并

5、按秩合并

 二、带权并查集

1、定义


一、并查集

        并查集是一种树形的数据结构,顾名思义,它用于处理一些不交集的合并 查询 问题。 它支持两种操作:
查找( Find ):确定某个元素处于哪个子集。
合并( Union ):将两个子集合并成一个集合。

 1、初始化

for (int i = 0; i < size; i++) fa[i] = i; // i 就在它本身的集合里

 2、查找

         一个故事:几个家族进行宴会,但是家族普遍长寿,所以人数众多。由于长时间的分离以 及年龄的增长,这些人逐渐忘掉了自己的亲人,只记得自己的爸爸是谁了,而最长者(称为【祖先】)的父亲已经去世,他只知道自己是祖先。为了确定自己是哪个家族,他们想出了一个办法:

        只要问自己的爸爸是不是祖先,一层一层的向上问,直到问到祖先。
        
        如果要判断两人是否在同一家族,只要看两人的祖先是不是同一人就可以了。 在这样的思想下,并查集的查找算法诞生了。

 

 

int find(int x) 
{ 
    if(fa[x] != x) return find(fa[x]); 
    return fa[x]; 
}
显然这样最终会返回x的祖先。

3、路径压缩 

        这样可以达成目的,但是显然效率实在太低。我们使用了太多没用的信息,我的祖先是谁与我父亲是谁没什么关系,这样一层一层找太浪费时间,不如我直接当祖先的儿子,问
一次就可以出结果了。甚至祖先是谁都无所谓,只要这个人可以代表我们家族就能得到想要的效果。把在路径上的每个节点都直接连接到根上 ,这就是路径压缩。

 

int find(int x) 
{ 
    if(fa[x] != x) fa[x] = find(fa[x]);//将祖宗变为其每个子节点的父亲 
    return fa[x];//最终返回祖宗节点 
}

 4、合并

        宴会上,一个家族的祖先突然对另一个家族说:我们两个家族交情这么好,不如合成一家好了。另一个家族也欣然接受了。我们之前说过,并不在意祖先究竟是谁,所以只要其中一个祖先变成另一个祖先的儿子就可以了。

 

 

void merge(int x, int y)
{ 
    int pa = find(x), pb = find(y);//找到x,y的祖宗 
    if(pa != pb) fa[pa] = pb;//祖宗不一样(即:x,y不在同一个集合);将pb作为pa 的父亲 
}

5、按秩合并

        合并时的小优化 -- 将一棵点数与深度都较小的集合树连接到一棵更大的集合树下。
在实际代码中,即便不使用启发式合并,代码也能够在规定时间内完成任务。
std::vector<int> size(N, 1); // 记录并初始化子树的大小为 1 
void merge(int x, int y) 
{ 
    int xx = find(x), yy = find(y); 
    if (xx == yy) return; 
    if (size[xx] > size[yy]) // 保证小的合到大的里 
    swap(xx, yy); 
    fa[xx] = yy; 
    size[yy] += size[xx]; 
}

 二、带权并查集

1、定义

        在并查集的边上定义某种权值 ( 一般定义 点到祖宗的距离 ) 、以及这种权值在路径压缩时产生的运 算,从而解决更多的问题。
        例如,对于经典的「NOI2001 」食物链,我们可以在边权上维护模 3 意义下的加法群。
int find(int x) 
{ 
    if(x != fa[x]) 
    { 
        int t = fa[x];//将x的父亲临时保存 
        fa[x] = find(fa[x]);//这是x的父亲已经变为祖宗 
        dis[x] += dis[t];//x原父亲t的距离已经变为到祖宗的距离,x到原父亲距离+原父亲到 祖宗距离 = x到祖宗距离 
    }
    return fa[x]; 
}

 

        食物链  

#include <iostream> 
#include <cstring> 
#include <algorithm> 
using namespace std; 
const int N = 5e4 + 10; 
int p[N], d[N]; int n, k; 

int find(int x) 
{ 
    if(x != p[x]) 
    { 
        int t = p[x]; 
        p[x] = find(p[x]); 
        d[x] += d[t]; }return p[x]; 
    }

int main() 
{ 
    cin >> n >> k; 
    for(int i = 0; i < n; i ++ ) 
    p[i] = i; int ans = 0; 
    while(k --) 
    { 
        int x, y, op; 
        cin >> op >> x >> y; 
        if(x > n || y > n) 
        { 
            ans ++; continue; 
        }
        int pa = find(x), pb = find(y); 
        if(op == 1)//判断x,y是否为同类(d[x]=d[y],在%3意义下) 
        { 
            if(pa == pb && ((d[x] - d[y]) % 3 + 3) % 3 != 0) ans ++;// 一个集合,但是不是同类 
            if(pa != pb) p[pa] = pb, d[pa] = ((d[y] - d[x]) % 3 + 3) % 3;//不在一个集合,将其合并 
        }
        else// 判断x是否捕食y(d[x]-d[y]=2,在%3意义下) 
        { 
            if(pa == pb && ((d[x] - d[y]) % 3 + 3) % 3 != 2) ans ++;// 一个集合,但不是捕食关系 
            if(pa != pb) p[pa] = pb, d[pa] = ((2 + d[y] - d[x]) % 3 + 3) % 3; 
        } 
    }
    cout << ans << '\n';
    return 0; 
}

  • 6
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

醉蕤

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值