首页 > 其他分享 >【学习笔记】并查集应用

【学习笔记】并查集应用

时间:2024-07-30 20:18:45浏览次数:21  
标签:关系 int 查集 笔记 学习 fa rel find

【学习笔记】并查集应用

NOI 2001 食物链 为例の两种并查集用法。

题目大意:

规定每只动物有且仅有三种可能的种类 \(A、B、C\),\(A\) 会吃 \(B\),\(B\) 会吃 \(C\),\(C\) 会吃 \(A\)。

给定 \(N\) 只动物,\(K\) 个语句。每个语句有如下两种可能的表达:

  1. 1 X Y 表示动物 \(X\) 与动物 \(Y\) 是同类。

  2. 2 X Y 表示动物 \(X\) 吃 \(Y\)。

每个语句可能是真话也可能是假话,每个语句是假话有三种可能:

  1. \(X\) 或 \(Y\) 比 \(N\) 大。

  2. 表达为 \(X\) 吃 \(X\)。

  3. 当前的话与前面的某些真的话冲突。

请求出 \(K\) 个语句里假话的总数。

种类并查集(扩展域并查集)

先推一个讲解

并查集能维护连通性、传递性,通俗地说,亲戚的亲戚是亲戚。

然而当我们需要维护一些对立关系,比如敌人的敌人是朋友时,正常的并查集就很难满足我们的需求。

这时,种类并查集就诞生了。

同个种类的并查集中合并,表达他们是朋友这个含义。

不同种类的并查集中合并,表达他们是敌人这个含义。


此题关系有三类(\(A、B、C\)),所以我们考虑建立 3 倍大小的并查集。其中 \(1 \sim n\) 表示种类 \(A\),\(n+1 \sim 2n\) 表示种类 \(B\),\(2n+1 \sim 3n\) 表示种类 \(C\)。

如果两只动物 \(x\) 和 \(y\) 是同类,那么就将 \(A_x\) 与 \(A_y\),\(B_x\) 与 \(B_y\),\(C_x\) 与 \(C_y\) 各并入一个集合内。

如果两只动物 \(x\) 吃 \(y\),那么就将 \(A_x\) 与 \(B_y\),\(B_x\) 与 \(C_y\),\(C_x\) 与 \(A_y\) 各并入一个集合内。

此时如果要表示动物 \(x\) 吃动物 \(y\),就说明 \(A_x\) 与 \(B_y\) 在同一集合中,根据对称性,其它的也一样,所以判断时只需要判一组。

  • \(x\) 与 \(y\) 同类与 \(x\) 吃 \(y\) 或 \(y\) 吃 \(x\) 矛盾。
  • \(x\) 吃 \(y\) 与 \(x\) 与 \(y\) 同类或 \(y\) 吃 \(x\) 矛盾。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 5e4+5;

int fa[N*3];

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

int main(){
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    int n, k, ans = 0; cin>>n>>k;
    for(int i=1; i<=n*3; i++)
        fa[i] = i;
    while(k--){
        int op, x, y; cin>>op>>x>>y;
        if(x==y&&op==2 || x>n || y>n){
            ans++;
            continue;
        }
        if(op==1){
            if(find(x)==find(y+n) || find(y)==find(x+n)){
                ans++;
                continue;
            }
            fa[find(x)] = fa[find(y)];
            fa[find(x+n)] = fa[find(y+n)];
            fa[find(x+n+n)] = fa[find(y+n+n)];
        } else if(op==2){
            if(find(x)==find(y) || find(y)==find(x+n)){
                ans++;
                continue;
            }
            fa[find(x)] = fa[find(y+n)];
            fa[find(x+n)] = fa[find(y+n+n)];
            fa[find(x+n+n)] = fa[find(y)];
        }
    }
    cout<<ans;
    return 0;
}

带权并查集

每个点与其集合的根都有权重,以此来表达关系。

以此题为例,0 代表 \(x\) 与 \(fa_x\) 同类,1 代表 \(x\) 吃 \(fa_x\),2 代表 \(x\) 被 \(fa_x\) 吃。

重点在于如何更新权值和判断关系。权值更新肯定伴随并查集的更新。在下面的图中就如向量一般计算。

查找(路径压缩)

知道 \(x\) 与其根 \(fa[x]\) 的关系,\(fa[x]\) 与其根 \(fa[fa[x]]\) 的关系,可以推出 \(x\) 与 \(fa[fa[x]]\) 的关系。

注意这里要先更新 \(fa[x]\) 的权值(先 find(fa[x])),在更新 \(x\) 的权值(得先存下 \(fa[x]\),不然 \(fa[x]\) 会变)。

\[rel[x \rightarrow rt] = rel[x \rightarrow fa]+rel[fa \rightarrow rt] \]

find.png

合并

知道 \(x\) 与 \(fa[x]\) 的关系,\(y\) 与 \(fa[y]\) 的关系,以及 \(x\) 与 \(y\) 之间的关系,就可以知道 \(fa[x]\) 和 \(fa[y]\) 的关系。

注意是 \(fa[x]\) 并到 \(fa[y]\) 上还是 \(fa[y]\) 并到 \(fa[x]\) 上。以下是 \(fa[x]\) 并到 \(fa[y]\) 上。

\[rel[fa[x]] = rel[y]-rel[x]+rel[x \rightarrow y] \]

merge.png

判断关系(是否矛盾)

知道 \(x、y\) 与根的关系,就能推出 \(x\) 与 \(y\) 的关系。(此时 \(x\) 与 \(y\) 已经在同一个集合内)

\[rel[x \rightarrow y] = rel[x]-rel[y] \]

check.png

以上操作取模时注意减法,显然此题模数为 \(3\)。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 50005;

int fa[N], rel[N];
// relation 存与根的关系
// 0--同类,1--能吃,2--被吃
const int p = 3;
int n, k, ans;

void init(){
    for(int i=1; i<=n; i++){
        fa[i] = i;
        rel[i] = 0;
        // 初始化跟自己的关系是同类
    }
}

int find(int x){
    if(fa[x] == x) return x;
    // 知道 x 与 fa[x] 的关系,fa[x] 与根的关系,可以推出 x 与根的关系
    // rel[x->rt] = rel[x->fa]+rel[fa->rt]
    int f = fa[x];
    fa[x] = find(fa[x]);
    rel[x] = (rel[x]+rel[f])%p;
    // 必须得分开写,因为原来的 fa[x] 与根的关系会在 find(fa[x]) 的时候更新
    return fa[x];
}

void merge(int u, int v, int r){
    // U与rtU的关系,V与rtV的关系,以及UV之间的关系,就可以知道rtU和rtV的关系。
    // rtU 并到 rtV 上
    // rel[ru] = rel[v]-rel[u]+rel[u->v]
    int ru = find(u), rv = find(v);
    if(ru != rv){
        fa[ru] = rv;
        rel[ru] = (rel[v]-rel[u]+r+p)%p;
    }
}

bool check(int x, int y, int r){
    if(x>n || y>n) return false; // 不能比 n 大
    if(x==y && r==1) return false; // 不能吃自己
    if(find(x)==find(y)){
        // 知道x、y与根的关系,就能推出 x 与 y 的关系
        // rel[x->y] = rel[x]-rel[y]
        return r == (rel[x]-rel[y]+p)%p;
    }
    return true;
    // 还没明确的关系就是可行的
}

int main(){
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    cin>>n>>k;
    init();
    while(k--){
        int op, x, y; cin>>op>>x>>y;
        if(check(x, y, op-1)){
            merge(x, y, op-1);
        } else{
            ans++;
        }
    }
    cout<<ans;
    return 0;
}

标签:关系,int,查集,笔记,学习,fa,rel,find
From: https://www.cnblogs.com/FlyPancake/p/18333279

相关文章

  • pre/post gate sim 仿真笔记
    在芯片研发阶段至少存在三种仿真,只有在这三种仿真都通过后才可能进入到芯片的tapeout阶段,这三种仿真分别是rtl功能级仿真、综合后网表仿真(pregatesim)、PR后网表仿真(postgatesim)。下面简单记录一下两种gatesim仿真。不论是pre还是post的gatesim都是门级网表的仿真,进行门级......
  • c语言笔记(2024.7.24)第三天
    常量与变量概念:·表面:程序运行过程中取值可以改变的数据·实际:变量其实代表了一块内存区域/单元/空间。变量名可视为该区域的标识。整个变量分为三部分:·变量名:这个只是变量的一个标识,我们借助变量名来存取数据。·变量空间/存储单元:这个就是内存中分配的一块用来存放......
  • 暑期学习C语言第一天完整版
    回顾今日成果:一、scanf语句的掌握我们可以看一看这道题,只是一个简单的整数输入、输出。在这之中,我们就可以利用scanf、printf语句,在使用scanf语句我们需要注意:scanf(“%d”,&a);printf("%d",a);其中关键点为,在使用scanf时,%d在双引号里面和&a中&是我们容易遗忘。二......
  • 位运算卷积学习笔记
    位运算卷积学习笔记位运算卷积,即快速沃尔什变换\(\text{FWT}\)和快速莫比乌斯变换\(\text{FMT}\),但事实上最常用的是\(\text{FWT}\),因为\(\text{FMT}\)所求解的内容是\(\text{FWT}\)的子集。位运算卷积首先要知道位运算卷积指的是\[c_i=\sum_{j\odotk=i}a_jb_k\]形......
  • Ansible 学习与扩展整理
    一、Ansible基础知识回顾核心组件主机清单(HostInventory):定义了Ansible可以管理的目标机器列表。模块(Modules):Ansible执行特定任务的最小单位,类似于命令行工具或脚本。插件(Plugins):扩展Ansible功能,如连接插件、回调插件等。Playbook:YAML格式的文件,定义了Ansi......
  • 【调试笔记-20240730-Linux-OpenWrt 23.05 安装 Docker 配置 bitnami/Wordpress-with-
    调试笔记-系列文章目录调试笔记-20240730-Linux-OpenWrt23.05安装Docker配置bitnami/Wordpress-with-NGINX实现微信用户在线注册登录文章目录调试笔记-系列文章目录调试笔记-20240730-Linux-OpenWrt23.05安装Docker配置bitnami/Wordpress-with-NGINX实现......
  • ssy中学暑假集训分数规划笔记
    分数规划出现在了我们今天的模拟赛中,看在这个名字深得我心而且我能看懂证明和内容的份上,给开个专题吧!\(1.定义\):分数规划就是求分数的极值。形象一点就是,给出\(a_i\)和\(b_i\),求一组\(w_i\in\{0,1\}\)最小化或者最大化下面的算式:\[\frac{\sum_{i=1}^{n}a_i*w_i}{\sum_{i=1}......
  • c语言第七天笔记
    作业题:设计TVM(地铁自动售票机)机软件。输入站数,计算费用,计费规则,6站2元,7-10站3元,11站以上为4元。输入钱数,计算找零(找零时优先找回面额大的钞票),找零方式为各种面额张数,可识别面额:100,50,20,10,5,1案例代码:运行效果:循环结构什么是循环代码的重复执行,就叫做循环。循......
  • Electron学习笔记(二)Hello World
    目录前言运行主进程创建界面使用窗口打开界面管理窗口的生命周期关闭所有窗口时退出应用(Windows&Linux)​如果没有窗口打开则打开一个窗口(macOS)使用预加载脚本访问渲染器的Node.js添加你自己的功能完整代码展示效果展示前言接上一篇文章Electron学习笔......
  • 笔记:从Aurora 8b/10b 到Aurora 64b/66b (一):Aurora 8b/10b
    参考:https://www.xilinx.com/products/intellectual-property/aurora8b10b.html#documentationhttps://docs.amd.com/r/en-US/pg046-aurora-8b10bhttps://docs.amd.com/v/u/en-US/aurora_8b10b_ds797https://mp.weixin.qq.com/s/gT4QUgvoFF6UI0PAhfEPvQ补丁:Aurora系IP内部......