AcWing 240. 食物链

AcWing 240. 食物链

一、题目描述

动物王国中有三类动物 A,B,C,这三类动物的食物链构成了有趣的环形。

ABBCCA

现有 N 个动物,以 1N 编号。

每个动物都是 A,B,C 中的一种,但是我们并不知道它到底是哪一种。

有人用两种说法对这 N
个动物所构成的食物链关系进行描述:

第一种说法是 1 X Y,表示 XY 是同类。

第二种说法是 2 X Y,表示 XY

此人对 N 个动物,用上述两种说法,一句接一句地说出 K 句话,这 K 句话有的是真的,有的是假的。

当一句话满足下列三条之一时,这句话就是假话,否则就是真话。

当前的话与前面的某些真的话冲突,就是假话;
当前的话中 XYN 大,就是假话;
当前的话表示 XX,就是假话。
你的任务是根据给定的 NK 句话,输出假话的总数。

输入格式
第一行是两个整数 NK,以一个空格分隔。

以下 K 行每行是三个正整数 DXY,两数之间用一个空格隔开,其中 D 表示说法的种类。

D=1,则表示 XY 是同类。

D=2,则表示 XY

输出格式
只有一个整数,表示假话的数目。

数据范围
1N50000,0K100000

输入样例:

100 7
1 101 1 
2 1 2
2 2 3 
2 3 3 
1 1 3 
2 3 1 
1 5 5

输出样例:

3

本题目是一道非常好的并查集练习题,有两种并查集的解法,分别是扩展域并查集、带权并查集,下面分别进行介绍:

二、扩展域并查集

1、三点认知

  • 两个同类元素的天敌集合是同一个集合,猎物集合也是同一个集合。

  • 天敌的天敌是猎物 (因为是 食物环 嘛)

  • 猎物的猎物是天敌 (因为是 食物环 嘛)

2、扩展域思想

1n个元素扩大为13n个元素,使用[13n]个并查集(每一个并查集中的所有元素都具有同一种特性,不同并查集中不存在相同元素)来维护3n元素彼此的关系。

为了形象化思考问题,我们假设三种动物,互为食物链:老鼠、大象、狐狸
关系为:

  • 狐狸 杀死 老鼠
  • 大象 拍死 狐狸
  • 老鼠 钻鼻子 大象

在这里x元素,x+n元素,x+2n元素三者的关系被定义为:

  • x元素的p[x]代表x家族

  • x+n元素的p[x+n]代表x的天敌家族

  • x+2n元素的p[x+2n]代表x的猎物家族

对于一句真话:

  • x,y是同类

    • 将他们的天敌集合(x+ny+n所在集合)合并
    • 将猎物集合(x+2n元素与y+2n元素所在集合)合并
    • x,y所在的集合 合并
  • xy的天敌

    • ① 将x家族与y的天敌家族合并
    • ② 将y家族和x的猎物家族合并
    • ③ 将x的天敌家族和y的猎物家族合并

3、实现代码

#include <bits/stdc++.h>
using namespace std;
const int N = 150010;
int p[N];
int ans;
int find(int x) {
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}
void join(int x, int y) {
    int f1 = find(x), f2 = find(y);
    if (f1 != f2) p[f1] = f2;
}
int main() {
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= 3 * n; i++) p[i] = i; // 并查集初始化

    while (m--) {
        int x, y, t;
        cin >> t >> x >> y;
        // 说出大于n编号的动物,肯定是假话
        if (x > n || y > n) {
            ans++;
            continue;
        }
        if (t == 1) { // x和y同类
            if (find(x + n) == find(y) || find(x + 2 * n) == find(y)) {
                ans++;
                continue;
            }
            join(x, y);
            join(x + n, y + n);
            join(x + 2 * n, y + 2 * n);
        } else { // x吃y
                 // +n  天敌,+2n 食物
            if (find(x) == find(y) || find(x + n) == find(y)) {
                ans++;
                continue;
            }
            join(x, y + n);
            join(x + n, y + 2 * n);
            join(x + 2 * n, y);
        }
    }
    printf("%d\n", ans);
    return 0;
}

二、带权并查集

功能:查询祖先+修改父节点为祖先+更新节点到根的距离(通过到父节点的距离累加和)
d[i]的含义:第 i 个节点到其父节点距离。

https://cdn.acwing.com/media/article/image/2021/01/08/2675_ab3bf90451-3.jpg

C++ 代码

#include <bits/stdc++.h>

using namespace std;

const int N = 50010;
const int M = 3;

/**
 带权并查集:
 (1)不管是同类,还是吃与被吃的关系,都把它们放到同一个链中去
 (2)还没有关联关系的两条链,是两条彼此独立的链
 (3)同一个链中动物关系用距离根结点的长度来描述: 0:同类,1:吃, 2:被吃
 通过上面的记录关系,就可以推理出任何两个节点的关系
 */

int n, m;
int p[N]; // 家族
int d[N]; // i结点到父结点的距离
int res;  // 假话的个数

// 带权并查集find模板
// ① 需要将原始并查集的find模板一拆为二
// ② 在拆分的两句话中间,增加更新到父节点距离的代码
int find(int x) {
    if (p[x] != x) {
        int t = find(p[x]);
        d[x] = (d[p[x]] + d[x]) % M; // 父亲到根据的距离+自己到父亲的距离=自己到根的距离
        p[x] = t;
    }
    return p[x];
}

int main() {
    cin >> n >> m;
    // 并查集初始化
    for (int i = 1; i <= n; i++) p[i] = i;

    // m个条件
    while (m--) {
        int t, x, y;
        cin >> t >> x >> y;
        // 如果出现x,y的序号,大于最大号,那么肯定是有问题,是假话
        if (x > n || y > n) {
            res++;
            continue;
        }
        // 祖先
        int px = find(x), py = find(y);

        if (t == 1) {                          // x,y同类
            if (px != py) {                    // 没有处理过 x,y的关系
                p[px] = py;                    // 记录x,y的关系,把两个链合并一下,px认py为家族族长
                d[px] = (d[y] - d[x] + M) % M; // x,y是同类,则d[px] + d[x]= 0 + d[y]
            } else if ((d[x] - d[y] + M) % M)  // 在同一个家族中,x,y同类,则d[x]=d[y],即d[x]-d[y]=0,不等于0,假话
                res++;
        }

        if (t == 2) {                              // x吃y
            if (px != py) {                        // 没有处理过x,y的关系
                p[px] = py;                        // 记录x,y的关系,把两个链合并一下,px认py为家族族长
                d[px] = (d[y] + 1 - d[x] + M) % M; // x吃y,则d[px]+d[x]-1=d[y]
            } else if ((d[x] - d[y] - 1 + M) % M)  // 在同一个家族中,x吃y,则d[x]-d[y]=1,即d[x]-d[y]-1=0,要是不等于0,假话
                res++;
        }
    }
    // 输出
    printf("%d\n", res);
    return 0;
}

代码解释:

posted @   糖豆爸爸  阅读(519)  评论(1编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
历史上的今天:
2017-09-15 Centos查找大文件的办法
Live2D
点击右上角即可分享
微信分享提示