题解:AcWing 240 食物链

【题目来源】

AcWing:240 食物链 - AcWing题库

【题目描述】

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

\(A\)\(B\)\(B\)\(C\)\(C\)\(A\)

现有\(N\)个动物,以\(1\sim N\)编号。

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

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

第一种说法是 1 X Y,表示 \(X\)\(Y\) 是同类。

第二种说法是 2 X Y,表示 \(X\)\(Y\)

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

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

  1. 当前的话与前面的某些真的话冲突,就是假话;

  2. 当前的话中\(X\)\(Y\)\(N\)大,就是假话;

  3. 当前的话表示\(X\)\(X\),就是假话。

你的任务是根据给定的\(N\)\(K\)句话,输出假话的总数。

【输入】

第一行是两个整数\(N\)\(K\),以一个空格分隔。

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

\(D=1\),则表示\(X\)\(Y\)是同类。

\(D=2\),则表示\(X\)\(Y\)

【输出】

只有一个整数,表示假话的数目。

【输入样例】

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. 问题分析:给定 \(N\) 个动物和 \(K\) 条食物链关系描述,需要判断每条描述的真假。动物分为 \(A, B, C\) 三类,形成环形食物链(\(A\)\(B\)\(B\)\(C\)\(C\)\(A\))。每条描述要么声明两个动物同类,要么声明一个吃另一个。假话判定条件包括:与前面真话冲突、编号越界、\(X\)\(X\)。关键观察是:可以用带权并查集维护每个动物与根节点的相对关系(模 \(3\) 意义下的距离)。

  2. 算法选择

    • 带权并查集(Disjoint Set Union with Weight):用 \(d[x]\) 表示 \(x\) 到其父节点的距离,模 \(3\) 后表示 \(x\) 相对于根节点的种类(\(0\):同类,\(1\):被根吃,\(2\):吃根)
    • 路径压缩 + 距离更新:查找时递归更新每个节点到根节点的距离
  3. 关键步骤

    • 初始化\(p[i] = i\)\(d[i] = 0\)(每个动物自成一个集合,距离为 \(0\)
    • 查找 find(x)
      • \(p[x] \neq x\),递归找到根 \(u = \text{find}(p[x])\)
      • \(d[x] += d[p[x]]\)(累加父节点到根的距离)
      • \(p[x] = u\)(路径压缩)
      • 返回 \(u\)
    • 处理操作
      • 假话判定
        • \(x > N\)\(y > N\)res++
        • \(t = 2\)\(x = y\)res++\(X\)\(X\)
      • 同类操作 \(t = 1\)\(X\)\(Y\) 同类)
        • \(\text{find}(x) = \text{find}(y)\)\((d[x] - d[y]) \bmod 3 \neq 0\)res++(在同一集合但种类不同)
        • 若不在同一集合:合并,\(p[px] = py\)\(d[px] = d[y] - d[x]\)(使 \(d[x] \equiv d[y] \pmod 3\)
      • 捕食操作 \(t = 2\)\(X\)\(Y\)
        • \(\text{find}(x) = \text{find}(y)\)\((d[x] - d[y] - 1) \bmod 3 \neq 0\):`res++$(在同一集合但 \(X\) 不吃 \(Y\)
        • 若不在同一集合:合并,\(p[px] = py\)\(d[px] = d[y] + 1 - d[x]\)(使 \(d[x] - d[y] \equiv 1 \pmod 3\),即 \(X\)\(Y\) 高一级)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(K \cdot \alpha(N))\),其中 \(\alpha\) 为阿克曼函数的反函数,近似常数
    • 空间复杂度:\(O(N)\),并查集数组
  5. 带权并查集的核心思想

    • 相对关系编码:用 \(d[x] \bmod 3\) 表示 \(x\) 相对于根节点的种类:\(0\) 表示同类,\(1\) 表示被根吃(根吃 \(x\)),\(2\) 表示吃根(\(x\) 吃根)。这种编码天然满足环形食物链的传递性
    • 路径压缩的距离累加find\(d[x] += d[p[x]]\) 确保路径压缩后 \(d[x]\) 仍表示 \(x\) 到新根的距离,维护了权值的正确性
    • 合并时的权值推导:合并两个集合时,通过代数推导确保新连接的权值满足给定的关系约束。例如同类操作要求 \(d[x] \equiv d[y] \pmod 3\),因此 \(d[px] = d[y] - d[x]\)
    • 模运算的一致性:所有距离关系在模 \(3\) 意义下成立,利用 \((a - b) \bmod 3\) 判断两个节点是否同类、捕食或被捕食关系
    • 适用于带相对关系的动态连通性问题、种类并查集、模意义下的等价类维护等场景

【解题思路】

【算法标签】

并查集

【代码详解】

#include <bits/stdc++.h>
using namespace std;

const int N = 50010;  // 定义最大节点数

int n, m;      // n: 节点数量, m: 操作次数
int p[N];      // 并查集父节点数组
int d[N];      // 存储节点到父节点的距离(带权)

// 查找x的根节点(带路径压缩和距离更新)
int find(int x)
{
    if (p[x] != x)
    {
        int u = find(p[x]);  // 递归找到根节点
        d[x] += d[p[x]];     // 更新当前节点到根节点的距离
        p[x] = u;            // 路径压缩
    }
    return p[x];
}

int main()
{
    scanf("%d%d", &n, &m);  // 输入节点数和操作数

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

    int res = 0;  // 记录假话数量

    while (m--)   // 处理每个操作
    {
        int t, x, y;
        scanf("%d%d%d", &t, &x, &y);

        // 检查节点编号是否合法
        if (x > n || y > n)
        {
            res++;  // 节点编号越界即为假话
        }
        else
        {
            int px = find(x), py = find(y);

            if (t == 1)  // 操作1:x和y是同类
            {
                // 如果已在同一集合但距离差不是3的倍数(不是同类)
                if (px == py && (d[x] - d[y]) % 3)
                    res++;
                else if (px != py)  // 不同集合则合并
                {
                    p[px] = py;
                    d[px] = d[y] - d[x];  // 维护距离关系
                }
            }
            else  // 操作2:x吃y
            {
                // 如果已在同一集合但距离差不满足x吃y的关系
                if (px == py && (d[x] - d[y] - 1) % 3)
                    res++;
                else if (px != py)  // 不同集合则合并
                {
                    p[px] = py;
                    d[px] = d[y] + 1 - d[x];  // 维护距离关系
                }
            }
        }
    }

    printf("%d\n", res);  // 输出假话总数
    return 0;
}

【运行结果】

100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
3
posted @ 2026-07-02 22:46  团爸讲算法  阅读(7)  评论(0)    收藏  举报