【并查集】食物链

题目描述——洛谷P2024

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

现有 N N N 个动物,以 1 ∼ N 1 \sim N 1N 编号。每个动物都是 A , B , C A,B,C A,B,C 中的一种,但是我们并不知道它到底是哪一种。

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

  • 第一种说法是 1 X Y,表示 X X X Y Y Y 是同类。
  • 第二种说法是2 X Y,表示 X X X Y Y Y

此人对 N N N 个动物,用上述两种说法,一句接一句地说出 K K K 句话,这 K K K 句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。

  • 当前的话与前面的某些真的话冲突,就是假话;
  • 当前的话中 X X X Y Y Y N N N 大,就是假话;
  • 当前的话表示 X X X X X X,就是假话。

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

输入格式

第一行两个整数, N , K N,K N,K,表示有 N N N 个动物, K K K 句话。

第二行开始每行一句话(按照题目要求,见样例)

输出格式

一行,一个整数,表示假话的总数。

样例 #1

样例输入 #1

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

样例输出 #1

3

提示

对于全部数据, 1 ≤ N ≤ 5 × 1 0 4 1\le N\le 5 \times 10^4 1N5×104 1 ≤ K ≤ 1 0 5 1\le K \le 10^5 1K105


1、题意分析:

如果 “A” 吃 “B”,“B” 吃 “C”,那么一定可以推断出 “C” 吃 “A”。因为题意已经说明了三种动物构成一个环形。

2、具体分析:

在这里插入图片描述
(1)、如图所示:“A” 节点到 “根节点” 的距离为 “1”,即 “1% 3 = 1”,所以 “A” 可以吃 “根节点”;“B” 节点到 “根节点” 的距离为 “2”,即 “2 % 3 = 2”,所以 “B” 能吃 “A”,那因此也就推断 “B” 也会被 “根节点” 吃;“C” 节点到 “根节点” 的距离为 “3”,即 “3 % 3 = 0”,所以 “C” 和 “根节点” 是同类。

(2)、用每个节点到 “根节点” 的距离来表示该节点与 “根节点” 之间的关系。因为此题是 “三种动物” 形成一个循环,所以我们通过每个节点到 “根节点” 的距离模 “3” 产生的结果来表示该节点与 “根节点” 之间的关系。关系如下表所示:

余数为 “1”可以吃 “根节点”
余数为 “2”被 “根节点” 吃
余数为 “0”和 “根节点” 是同类

3、距离的含义(形象的解释):

在这里插入图片描述

(1)、假设 “根节点” 为 “第0代”,如果 “x” 吃 “根节点”,那么 “x” 就表示为 “第1代”,即 “x” 到 “根节点” 距离为 “1”;

(2)、如果 “y” 吃 “x”,那么 “y” 就表示为 “第2代”,即 “y” 到 “根节点” 距离为 “2”;

(3)、如果 “z” 吃 “y”,那么 “z” 就表示为 “第3代”,即 “z” 到 “根节点” 距离为 “3”。

此时就可以将到 “根节点” 的距离看成 “第几代”。如果到 “根节点” 的距离为 “5”,那么就是 “第5代”。由此可以推论出:“第0代” 和 “第3代” 是同类,“第1代” 和 “第4代” 是同类,“第2代” 和 “第5代” 是同类。

4、维护距离:

在这里插入图片描述
(1)、维护距离时,只能维护当前节点到它的父节点间的距离。“C” 到父节点(“B”)的距离为 “2”;“B” 到父节点(“A”)的距离为 “3”;“A” 到父节点(“根节点”)的距离为 “5”。

(2)、虽然维护的是当前节点到它的父节点间的距离,但我们通过 “路径压缩” 后,每个节点的父节点都会变成 “根节点”,那么,此时的距离就表示该节点到 “根节点” 的距离。比如:“C” 没有 “路径压缩” 前,此时的距离就表示 “C” 到父节点(“B”)的距离为 “2”,经过 “路径压缩” 后,“C” 的父节点变成了 “根节点”,那么此时的距离就表示 “C” 到 “根节点” 的距离,距离为 “10”。

5、距离计算:

在这里插入图片描述
通过路径压缩,让每个节点的父节点都会变成 “根节点”,那么,此时的距离就表示该节点到 “根节点” 的距离。即让 “x” 到 “父节点” 的距离更新成 “x” 到 “根节点” 的距离。

如图所示:“d[x]” 表示 “x” 到 “x” 的父节点的距离,通过 “路径压缩”,要让 “d[x]” 更新成 “x” 到 “根节点” 的距离。没更新 “d[x]” 前,“d[x]” 就表示 “x” 的父节点(“p[x]”)的距离,“d[p[x]]” 就表示 “x” 的父节点(“p[x]”)到 “根节点” 的距离,所以 “d[x] + d[p[x]]” 就等于 “x” 到 “根节点” 的距离。所以将 “d[x]” 更新成 “d[x] + d[p[x]]” 就表示 “x” 到 “根节点” 的距离 。

6、"find"函数进行递归的过程:

在这里插入图片描述递归过程:

①、在 “find(1)” 中,“1” 的父节点为 “2”,所以 “p[1] = 2”,则 “u = find(p[1])”,相当于 “u = find(2)”。

②、在 “find(2)” 中,“2” 的父节点为 “3”,所以 “p[2] = 3”,则 “u = find(p[2])”,相当于 “u = find(3)”。

③、在 “find(3)” 中,“3” 的父节点为 “4”,所以 “p[3] = 4”,则 “u = find(p[3])”,相当于 “u = find(4)”。

④、在 “find(4)” 中,“4” 的父节点为 “4”,所以 “p[4] = 4”,说明 “4” 是根节点,这时就结束递归,开始回溯(return p[4])

回溯过程:

①、回溯到 “find(3)” 中, “3” 的父节点变成 “4”(根节点),“d[3] = d[3] + d[4] = 1 + 0 = 1”, “p[3] = 4”,然后回溯返回 “p[3]”。

②、回溯到 “find(2)” 中, “2” 的父节点变成 “4”(根节点),“d[2] = d[2] + d[3] = 2 + 1 = 3”, “p[2] = 4”,然后回溯返回 “p[2]”。

③、回溯到 “find(1)” 中, “1” 的父节点变成 “4”(根节点),“d[1] = d[1] + d[2] = 3 + 3 = 6”, “p[1] = 4”,然后回溯返回 “p[1]”。

至此,就将 “1”,“2”,"3"的父节点都变成了 “4”(根节点),实现 “路径压缩”;并且 “x” 到 “父节点” 的距离更新成 “x” 到 “根节点” 的距离。

7、针对"x"和"y"是同类,但它们不在同一个集合中,距离如何处理?

在这里插入图片描述
“x” 到 “根节点” 的距离为 “d[x]”,“y” 到 “根节点” 的距离为 “d[y]”,现在合并 “x” 和 “y” 所在的集合,让 “x” 所在集合的 “根节点” 认 “y” 所在集合的 “根节点” 为父亲。既然 “x” 和 “y” 是同类,即 “(d[x] + ? - d[y]) % 3 = 0”,所以 “? = d[y] - d[x]”。

因为 “x” 所在集合的 “根节点” 认 “y” 所在集合的 “根节点” 为父亲,所以 “x” 的 “根节点” 就变成了 “py”,那么 “x” 到新的 “根节点” 的距离就等于 “d[x] + ?”。

8、针对"x"吃"y",但它们不在同一个集合中,距离如何处理?

在这里插入图片描述
“x” 到 “根节点” 的距离为 “d[x]”,“y” 到 “根节点” 的距离为 “d[y]”,现在合并 “x” 和 “y” 所在的集合,让 “x” 所在集合的 “根节点” 认 “y” 所在集合的 “根节点” 为父亲。既然 “x” 吃 “y” ,即 “(d[x] + ? - d[y] - 1) % 3 = 0”,所以 “? = d[y] + 1 - d[x]”。

因为 “x” 所在集合的 “根节点” 认 “y” 所在集合的 “根节点” 为父亲,所以 “x” 的 “根节点” 就变成了 “py”,那么 “x” 到新的 “根节点” 的距离就等于 “d[x] + ?”。又因为 “x” 吃 “y”,那么 “x” 到 “根节点” 的距离比 “y” 到 “根节点” 的距离多 “1”。

9、代码实现:

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 50010;

int n, m;
int p[N], d[N];  // "p[x]"表示当前"x"节点的父节点。"d[x]"存储"x"到"x"的父节点的距离。

int find(int x)
{
    if (p[x] != x)
    {
        int u = find(p[x]);  // 让"u"临时存储"根节点"
        d[x] = d[x] + d[p[x]];  // 通过路径压缩,要让"x"到"父节点"的距离("d[x]")更新成"x"到"根节点"的距离。
        p[x] = u;  // 更新完距离后,此时让"x"的父节点等于"根节点"。
    }

    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;  // "t"表示说法的种类
        scanf("%d%d%d", &t, &x, &y);

        if (x > n || y > n)
        {
            res++;
        }
        else
        {
            int px = find(x), py = find(y);
            if (t == 1)
            {
                // 如果"x"和"y"是同类,那么它们到"根节点"的距离相减后,再模"3",结果一定是等于"0"。
                if (px == py && (d[x] - d[y]) % 3 != 0)
                {
                    res++;
                }
                else if (px != py)  // 表示"x"和"y"不在同一个集合中,
                {
                    p[px] = py;
                    d[px] = d[y] - d[x];  // 具体过程看"步骤(7)"
                }
            }
            else
            {
                // "x"吃"y",所以"x"到"根节点"的距离比"y"到"根节点"的距离多"1"。
         		if (px == py && (d[x] - d[y] - 1) % 3 != 0)
                {
                    res++;
                }
                else if (px != py)
                {
                    p[px] = py;
                    d[px] = d[y] + 1 - d[x];  // 具体过程看"步骤(8)"
                }
            }
        }
    }

    printf("%d\n", res);

    system("pause");
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值