[NOI2001] 食物链

题目描述

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

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

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

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

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

  • 当前的话与前面的某些真的话冲突,就是假话;
  • 当前的话中 XX 或 YY 比 NN 大,就是假话;
  • 当前的话表示 XX 吃 XX,就是假话。

你的任务是根据给定的 NN 和 KK 句话,输出假话的总数。

输入格式

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

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

输出格式

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

输入输出样例

输入 #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\le N\le 5 \times 10^41≤N≤5×104,1\le K \le 10^51≤K≤105。

本题解适合于并查集初学者,建议嫌文章冗长的巨佬们阅读其他题解。


引入

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

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

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

常见的做法是将原并查集扩大一倍规模,并划分为两个种类。

在同个种类的并查集中合并,和原始的并查集没什么区别,仍然表达他们是朋友这个含义。

考虑在不同种类的并查集中合并的意义,其实就表达 他们是敌人 这个含义了。

按照并查集美妙的 传递性,我们就能具体知道某两个元素到底是 敌人 还是 朋友 了。

至于某个元素到底属于两个种类中的哪一个,由于我们不清楚,因此两个种类我们都试试。

具体实现,详见 P1525 关押罪犯


概念解释

再来看本题,每个动物之间的关系就没上面那么简单了。

对于动物 xx 和 yy,我们可能有 xx 吃 yy,xx 与 yy 同类,xx 被 yy 吃。

但由于关系还是明显的,11 倍大小、22 倍大小的并查集都不能满足需求,33 倍大小不就行了!

类似上面,我们将并查集分为 33 个部分,每个部分代表着一种动物种类。

设我们有 nn 个动物,开了 3n3n 大小的种类并查集,其中 1 \sim n1∼n 的部分为 AA 群系,n + 1 \sim 2nn+1∼2n 的部分为 BB 群系,2n + 1 \sim 3n2n+1∼3n 的部分为 CC 群系。

我们可以认为 AA 表示中立者,BB 表示生产者,CC 表示消费者。此时关系明显:AA 吃 BB,AA 被 CC 吃。

当然,我们也可以认为 BB 是中立者,这样 CC 就成为了生产者,AA 就表示消费者。(还有 11 种情况不提及了)

联想一下 22 倍大小并查集的做法,不难列举出:当 AA 中的 xx 与 BB 中的 yy 合并,有关系 xx 吃 yy;当 CC 中的 xx 和 CC 中的 yy 合并,有关系 xx 和 yy 同类等等……

但仍然注意了!我们不知道某个动物属于 AA,BB,还是 CC,我们 33 个种类都要试试!

也就是说,每当有 11 句真话时,我们需要合并 33 组元素。

容易忽略的是,题目中指出若 xx 吃 yy,yy 吃 zz,应有 xx 被 zz 吃。

这个关系还能用种类并查集维护吗?答案是可以的。

若将 xx 看作属于 AA,则 yy 属于 BB,zz 属于 CC。最后,根据关系 AA 被 CC 吃可得 xx 被 zz 吃。

既然关系满足上述传递性,我们就能放心地使用种类并查集来维护啦。


图片解释

理论太难懂?那就结合数据和图片来解释吧!

假如我们有以下的输入数据:

4 5
1 1 3
2 2 4
2 3 2
1 1 4
2 2 1

因为涉及 4 个动物(n = 4n=4),所以构建初始并查集如下图:

先看第 11 句话:动物 11 和 33 是同类的。

我们可以在 33 个群系中分别给 11 和 33 的集合合并,以表示动物 11 和 33 是一定友好的。

再看第 22 句话:动物 22 吃 44。

显然这不是矛盾的。但我们不知道 22 和 44 对应 AA, BB,CC 中的哪个,所以我们只能根据 AA 吃 BB,合并 AA 群系中的 22 和 BB 群系中的 44;再根据 BB 吃 CC 和 CC 吃 AA,作出对应的处理。结果如下所示:

接着看第 33 句话:动物 33 吃 22。这是句真话,具体的真假话判断方法看下面两句话。我们暂且先作出以下处理:

第 44 句话中,表明 11 和 44 是同类动物。此时我再解释如何判断话的真假。

对于同类动物,我们转换一下,如果我们知道 11 不吃 44 且 44 不吃 11,他们不就同类了吗?

好,那我们的任务就变成:如何判断动物 xx 吃动物 yy?

反观第 22 句话,我们知道如果要表示动物 xx 吃动物 yy,只要根据 AA 吃 BB,把 AA 群系中的 xx 和 BB 群系中的 yy 合并即可。另外 22 次合并暂不讨论。

那反过来,如果 AA 群系中的 xx 已经和 B 群系中的 yy 在同一集合中了,不就表示了动物 xx 吃动物 yy 吗?

于是,我们看到上面那张图,BB 群系中的 11 按照并查集的递归操作,找出自己的终极上级是 AA 群系中的 44。

分析其含义,属于 BB 群系的 11 已经与 AA 群系的 44,应有 44 吃 11,而非同类。第 44 句话是假的。

那么第 55 句话,22 吃 11。我们需要判断 22 和 11 是否是同类并且 22 是否被 11 吃即可。

判断是否同类,我们同样可以反过来:判断在同个群系中的 22 和 11 的集合是否已经合并。

得出 22 和 11 不是同类后,我们再看 11 是否吃 22。看图,AA 群系中的 11 和 BB 群系中的 22 在同一集合中。

得出 11 吃 22。第 55 句话也是假话。

因此,答案就是有两句假话。输出 22,问题完美解决。


注意事项

  • 种类并查集求的并非具体种类,而是关系!

  • 在代码过程中,不要忘了特判编号大于 nn 的情况!


代码实现

如果还没有理解,只能使用最终办法了,上程序!

(当然我还是希望各位摸清种类并查集的本质,灵活运用)

#include <cstdio>

inline int read() {
	char c = getchar(); int n = 0;
	while (c < '0' || c > '9') { c = getchar(); }
	while (c >= '0' && c <= '9') { n = (n << 1) + (n << 3) + (c & 15); c = getchar(); }
	return n;
}

const int maxN = 100005;

int n, m, ans, fa[maxN * 3];

int find(int u) { return fa[u] == u ? u : fa[u] = find(fa[u]); }

int main() {
	n = read(), m = read();
	for (int i = 1; i <= n * 3; i++) { fa[i] = i; }
	for (; m; m--) {
		int opt = read(), u = read(), v = read();
		if (u > n || v > n) { ans++; continue; }
		if (opt == 1) {
			if (find(u + n) == find(v) || find(u) == find(v + n)) { ans++; }
			else {
				fa[find(u)] = find(v);
				fa[find(u + n)] = find(v + n);
				fa[find(u + n + n)] = find(v + n + n);
			}
		} else {
			if (find(u) == find(v) || find(u) == find(v + n)) { ans++; }
			else {
				fa[find(u + n)] = find(v);
				fa[find(u + n + n)] = find(v + n);
				fa[find(u)] = find(v + n + n);
			}
		}
	}
	printf("%d\n", ans);
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值