食物链是并查集中的一道经典题, 第一次看《挑战程序设计竞赛》上懵懵懂懂, 最近又看见了发现还是一脸懵逼。

首先题目如下

动物王国中有三类动物 A,B,C,这三类动物的食物链构成了有趣的环形。A 吃 B,B 吃 C,C 吃 A。现有 N 个动物,以 1∼N 编号。每个动物都是 A,B,C 中的一种,但是我们并不知道它到底是哪一种。有人用两种说法对这 N 个动物所构成的食物链关系进行描述:第一种说法是 1 X Y,表示 X 和 Y 是同类。第二种说法是 2 X Y,表示 X 吃 Y。此人对 N 个动物,用上述两种说法,一句接一句地说出 K 句话,这 K 句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。当前的话与前面的某些真的话冲突,就是假话;
当前的话中 X 或 Y 比 N 大,就是假话;
当前的话表示 X 吃 X,就是假话。
你的任务是根据给定的 N 和 K 句话,输出假话的总数。

第一次见可能不知道怎么用并查集维护这么多种集合, 甚至像我一样看了白书上的讲解还是不太懂, 下面是我重新思考了一下, 并看了y总的另一种讲解得出的一下总结。

一、维护所有的组合来判断关系

白书上的代码就是维护的所以组合, 但我一开始并没看懂。。。
首先我们先用一个3 * N 的数组建立并查集,每 N 个区间代表的是一种类型,想下面这样。

为了简洁, 下面对于三个区间分别称为 N, 2N, 3N

注意这里要抽象出来,不能理解为具体的A类、B类,因为题目中三者的关系是一个环, 所以只要记住 N 内的动物能吃 2N 内的动物,而 2N内的动物能吃 3N 内的动物, 然后 3N 内的动物能吃第 N 内的动物

然后在这个 3 * N 的并查集里面, 会有很多个集合, 而每一个集合都是一组关系, 举个栗子:
a 可以吃 b, 那么 a b 就是一个集合里的, 而上面的关系中有三种捕食关系, 所以 a 吃 b 有三种情况,一个是 a 是 N 内的, 则 b 就是2N内的(因为 N 可以吃 2N), 这时候数组中 a 和 b + N 就要成为一个集合;第二个是 a 是第 2N 内的, 则 b 就是 3N 内的, a + N 和 b + 2 * N 就是一个集合了; 同理第三种情况a + 2 * N 和 b 就是一个集合。

注意这里 a, b 在不同的 N 区域内的性质不一样, 虽然都是a, b,但是他们代表的是不同物种的a, b,也就是说如果 a 和 b + N 在一个集合里,则这个集合存在 a 吃 b 的关系, 如果 a 和 b + 2N 在同一组,则存在 b 吃 a 的关系,等等…

对于题目的具体做法 :

首先判断它们是否是一个集合中的,即判断是否已经有关系了
如果 x 和 y 在同一个集合中

这时候就要判断 xy 已有的关系是否和所给的关系矛盾, 例如 x 和 y + N (第二组中的 y) 是同一个集合, 这说明 x 能吃 y,因此 x 和 y 是同类与 y 能吃 x 就是谎言。

注意这里不需要判断另外两种关系是否合法(第二组的x和第三组的y,第三组的x和第一组的y),因为在下面合并过程中,我们把每种情况都保存下来了,如果 x 和 y + n 是一组,则必有 x + n 和 y + 2 * n 和 x + 2 * n 和 y 是同一组,所以只需要判断一种情况就行。

如果不在同一个集合

就需要把两个集合合并, 而他们是同类的可能有三种, 即同为第一种, 第二种和第三种。

具体代码如下 :

#include <iostream>
#include <string>
#include <algorithm>
#include <set>
#include <map>
#include <unordered_set>
#include <unordered_map>
#include <stack>
#include <queue>
#include <cstdlib>
#include <cstring>
using namespace std;#define mst(a, b) memset(a, b, sizeof(a))
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 100000 + 10;
const int inf = INT32_MAX;int n, k;
int fa[maxn * 3], cnt;int find(int x) {return fa[x] = fa[x] == x ? x : find(fa[x]);
}void merge(int x, int y) {fa[find(x)] = find(y);
}int main() {std::ios::sync_with_stdio(false);cin >> n >> k;for (int i = 0; i <= n * 3 + 10; i++) fa[i] = i;while (k--) {int d, x, y;cin >> d >> x >> y;if (x <= 0 || x > n || y <= 0 || y > n) {cnt += 1;continue;}if (d == 1) {if (find(x) == find(y + n) || find(x) == find(y + 2 * n)) {// 如果 x 能吃 y, 或者 x 能被 y 吃, 则为谎言cnt += 1;} else {// 否则 xy 合并为同一种merge(x, y); // 同为第一种merge(x + n, y + n); // 同为第二种merge(x + 2 * n, y + 2 * n); // 同为第三种}} else {if (find(x) == find(y) ||find(x) == find(y + 2 * n)) {// 如果 x 和 y 是同一种 或者 x 能被 y 吃, 则为谎言cnt += 1;} else {// 否则合并 x 能吃 ymerge(x, y + n); // 第一种生物的 x 能吃 ymerge(x + n, y + 2 * n); // 第一种生物x 能吃 ymerge(x + 2 * n, y); // ....}}}cout << cnt << endl;return 0;
}

二、通过根节点判断关系

这种方法是在 acWing 上y总讲的, 第一次听的时候有点懵,听第二遍才发现是真的妙,我把主要的思路说一下(强烈推荐y总的课,讲得算法很全很详细,而且价格也不是太贵)
因为关系是个三个节点的环,所以我们假设父节点被子节点吃,则可以确定子节点的子节点可以吃父节点,即:

注意这里说的根节点指的是并查集中每一个集合的祖先

然后可以发现能吃根节点的到根节点的距离为 1, 能被根节点吃的到根节点的距离为 2,和根节点属于同一类的距离为 3,如果向下多画几个几点,你会发现他们 模 3 之后的值分别是 1 2 0,所以只要确定了和根节点的距离,就可以确定他们之间的关系。
举个栗子:
如果 a 节点到 根节点的距离 mod 3 = 1,b 节点 mod 3 = 2,这说明 a 能吃根节点,b 能被 根节点吃,根据关系可以推出 b 能 吃 a,他们之间的距离关系就是 : dis(a) % 3 + 1 = dis(b) % 3 ⇒ (dis(a) - dis(b) + 1) % 3 = 0
(dis(b) - dis(a) - 1) % 3 = 0

dis(x) 表示 x 到根节点的距离

如果 a 节点和 b 节点 mod 3 相等,说明他们是同一类,距离关系就为:
dis(a) % 3 = dis(b) % 3 ⇒ (dis(a) - dis(b)) % 3 = 0

这样我们就可以用上面两个等式来判断 a 和 b 如果在一个集合中,他们的关系是同一类还是捕食。

在合并节点的时候需要更新他们到根节点的距离:
如果 a 和 b 是同一类

则他们合并之后 a、b 到根节点的距离 mod 3 还是为0,如果以 a 向 b 合并

则有 (dis(a) + D) % 3 = dis(b) % 3,即 (dis(a) + D - dis(b)) % 3 = 0
要使上面等式为0,则 D 明显为 dis(b) - dis(a),所以在合并的时候,a的距离只要更新为 dis(b) - dis(a) 就可以了

如果 a 吃 b

同理
则 (dis(a) - 1 + D) % 3 = dis(b) % 3 ⇒ (dis(a) + D - dis(b) - 1) % 3 = 0
所以只要更新 a 的距离为 dis(b) - dis(a) + 1 就彳亍。

具体代码:

#include <iostream>
#include <string>
#include <algorithm>
#include <set>
#include <map>
#include <unordered_set>
#include <unordered_map>
#include <stack>
#include <queue>
#include <cstdlib>
#include <cstring>
using namespace std;#define mst(a, b) memset(a, b, sizeof(a))
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 100000 + 10;
const int inf = INT32_MAX;int fa[maxn], d[maxn];
int n, k, cnt;int find(int x) {if (x != fa[x]) {int u = fa[x]; // 保存 x 的祖先fa[x] = find(fa[x]); // 更新 x 集合d[x] += d[u]; // 更新x 到根节点的距离(x 到自己以前的根节点的// 距离 + 以前根节点到新根节点的距离)}return fa[x];
}void merge(int x, int y) {fa[find(x)] = find(y);
}int main() {std::ios::sync_with_stdio(false);cin >> n >> k;for (int i = 0; i <= n; i++) fa[i] = i;while (k--) {int op, x, y;cin >> op >> x >> y;if (x > n || y > n) {cnt += 1;continue;}int fx = find(x), fy = find(y);if (op == 1) {if (fx == fy && (d[x] - d[y]) % 3) {// 如果 x 和 y 在同一集合, 且 x 和 y 不是同一类cnt++;} else if (fx != fy) {// 合并两个集合d[fx] = d[y] - d[x];fa[fx] = fy;}} else {if (fx == fy && (d[x] - d[y] - 1) % 3) {// 如果 x 和 y 在同一集合, 且 x 不能吃 ycnt++;} else if (fx != fy) {// 不是一个集合就合并d[fx] = d[y] - d[x] + 1;fa[fx] = fy;}}}cout << cnt << endl;return 0;
}

这种方法 y 总讲得真的非常详细,对竞赛有兴趣的可以考虑一下 acWing 的算法课。

食物链 (利用并查集的两种解决方法)相关推荐

  1. android中view手势滑动冲突的两种解决方法

    android中view手势滑动冲突的解决方法,主要解决方法有两种,外部和内部拦截.有需要的可以参考下. Android手势事件的冲突跟点击事件的分发过程息息相关,由三个重要的方法来共同完成,分别是: ...

  2. win7 u盘 正在计算机,U盘在Win7电脑上读不出来怎么办?两种解决方法全教给你!...

    在我们平日的工作或学习生活中,有很多小伙伴都会用U盘来存储学习或工作上比较重要的资料文件,虽然是现在使用U盘的小伙伴们是越来越少了吧,但是这也不能影响那些习惯使用U盘的小伙伴. 当我们想要处理U盘上的 ...

  3. 计算机名无法更改成中文,电脑磁盘名无法修改的两种解决方法

    电脑安装好系统之后都会对磁盘进行分区,不同的磁盘存放不一样的文件,一些用户为方便记忆,会将电脑磁盘改成自己喜欢的名称.但是一些用户遇到过电脑磁盘名无法修改的问题,怎么办呢?不要担心,阅读下文教程,只要 ...

  4. 桌面打不开计算机控制面板,电脑控制面板打不开怎么办?两种解决方法

    日常生活中,如果你遇到一些小的电脑故障,可以通过控制面板进行设置管理,尝试修复故障,如果打不开控制面板,势必影响正常办公.针对小伙伴反映的控制面板打不开的问题,大家可以参考以下两种解决方法. 电脑控制 ...

  5. 邮件服务器arp攻击,服务器的ARP欺骗攻击的防范的两种解决方法

    服务器的ARP欺骗攻击的防范的两种解决方法 更新时间:2008年01月10日 11:59:11   作者: 服务器的ARP欺骗攻击的防范的两种解决方法 服务器的ARP欺骗攻击的防范 这些天我的服务器几 ...

  6. c语言蓝屏代码大全,window_Win10系统出现蓝屏提示错误代码0x00000050两种解决方法,  刚刚升级Win10系统的一段 - phpStudy...

    Win10系统出现蓝屏提示错误代码0x00000050两种解决方法 刚刚升级Win10系统的一段时间,容易出现蓝屏的问题.导致蓝屏故障的原因各不相同,我们需要根据错误代码来执行正确的解决方法.比如,最 ...

  7. mysql workbench kernelbase.dll_电脑出现kernelbase.dll错误的两种解决方法

    KernelBase.dll是Windows操作系统的重要文件,它为各种应用程序提供服务.如果电脑提示kernelbase.dll错误,这该怎么处理?大家可以用电脑自带的防火墙或者是第三方软件来进行故 ...

  8. Json返回时间中出现乱码问题的两种解决方法

    Json返回时间中出现乱码问题的两种解决方法 参考文章: (1)Json返回时间中出现乱码问题的两种解决方法 (2)https://www.cnblogs.com/hanyinglong/archiv ...

  9. css之文本两端对齐的两种解决方法

    css之文本两端对齐的两种解决方法 参考文章: (1)css之文本两端对齐的两种解决方法 (2)https://www.cnblogs.com/wl0804/p/11265225.html 备忘一下.

最新文章

  1. Asp.Net MVC2.0 Url 路由入门---实例篇
  2. DVWA 黑客攻防实战(十五) 绕过内容安全策略 Content Security Policy (CSP) Bypass
  3. CV之YOLOv3:深度学习之计算机视觉神经网络Yolov3-5clessses训练自己的数据集全程记录
  4. PHP用CURL伪造IP和来源
  5. 计算机专业英语卷子,计算机专业英语A试卷.doc
  6. 【Python CheckiO 题解】Striped Words
  7. HALCON学习之旅(六)
  8. php 异步进度条,PHP学习:PHP+Ajax异步带进度条上传文件实例
  9. linux下配置socks 5代理
  10. ONVIF系列——Onvif协议介绍
  11. 抖音直播Web端框架及消息处理流程分析(新鲜出炉)
  12. Activiti工作流表结构详解
  13. 字体图标文件服务器提示404,iis环境下字体图标woff/woff2/svg返回404不显示的原因与解决方法...
  14. 计算机科学家刘欣,科学家都爱啥运动?
  15. eCharts二三维地图总结
  16. OpenWrt——进行PPPoE拨号时透过路由器访问光猫的方法
  17. 365智能云打印怎么样?365小票无线订单打印机好用吗?
  18. Android:安卓实现高考倒计时功能
  19. 朴素贝叶斯算法原理、代码实现原理、以及鸢尾花分类代码实现(详细代码原理讲解)
  20. RFID Hacking–资源大合集

热门文章

  1. Matlab 棋盘制作
  2. 如何理解CNN中的感受野(receptive-field)以及如何计算感受野?
  3. 字节鏖战美团的关键一役
  4. 理解和选择运行时安全自保护-RASP
  5. 微信公众平台开发-消息管理-发送客服消息
  6. 在为订单 7000009确定实际成本中出错
  7. Visual Paradigm在 Windows系统电脑上安装
  8. 杂记之罗翔语录:要爱具体的人,而不要爱抽象的人
  9. 木瓜从林。。。。。。。。。。。。。。。。。
  10. 计算机机房门尺寸,门的宽度是多少