【题解】食物链

题目

题目来源:CCF NOI2001;(模拟赛 T3)

评测地址:Luogu#2024

题目描述

动物王国中有三类动物 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\) 句话,输出假话的总数。

输入格式

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

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

输出格式

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

数据规模和约定

评测时间限制 \(1000\ \textrm{ms}\),空间限制 \(128\ \textrm{MiB}\)

\(1\le N\le 5\times 10^4\)\(1\le K\le 10^5\)

分析

题意是指有一组元素,每个元素有 \(3\) 种状态。现在给出一系列元素的状态依赖关系,形如「如果 \(A\)xxx,那么 \(B\) 就是 yyy,反之亦然。」

(考虑一下为什么这与原题是等价的,这很重要)

也就是说,这道题要求我们维护一个状态,其中有若干状态是 必须并存 的。这就是一个集合合并问题,可以使用并查集。

我们将一个动物拆分成 \(3\) 种状态,每一种就是一个节点。我们用并查集维护其中的并存关系。

为表示方便,下面用 \(A_t\) 表示一个节点:

  • A 表示节点编号,也就是动物编号 \(i\)
  • t 表示对应的类型,是 A、B、C 中的一个。

比如说 \(i\)\(j\) 是同类,那么就可以合并 \(i_A\)\(j_A\)\(i_B\)\(j_B\),以及 \(i_C\)\(j_C\)

那么如果 \(i\)\(j\),那么就可以合并 \(i_A\)\(j_B\)\(i_B\)\(j_C\),以及 \(i_C\)\(j_A\)

判断是否真假,只需要看看除了给出条件以外的 \(i\)\(j\) 节点是否存在同一集合的情况。有就是假话。

比如说已经知道了 \(i\)\(j\)\(j\)\(k\) 又是同类,可以得到 \(i_A\)\(k_B\) 在一个集合内。此时如果说给出 \(k\)\(i\)(即 \(k_B\)\(i_C\) 在同一集合内),就会矛盾。

这样,我们就可以利用并查集,在 \(\Theta(n\alpha(n))\) 的时间内愉快 AC。

Code

由于要判断,这次的代码有亿点长……

#include <cstdio>
#include <cctype>
using namespace std;

const int max_n = 50000;
int ufs[max_n*3]; // 并查集别忘了开 3 倍大小

inline int pack(int id, int lat) { return id + lat * max_n; } // 编号

int find(int x) // 查找,最好加上路径压缩
{
	if (x != ufs[x])
		ufs[x] = find(ufs[x]);
	
	return ufs[x];
}

void unite(int a, int b) // 合并
{
	ufs[find(a)] = find(b);
}

bool isn_1(int a, int b) // 是否(不)是同类
{
	if (find(pack(a, 0)) == find(pack(b, 1)))
		return true;
	if (find(pack(a, 0)) == find(pack(b, 2)))
		return true;
	if (find(pack(a, 1)) == find(pack(b, 0)))
		return true;
	if (find(pack(a, 1)) == find(pack(b, 2)))
		return true;
	if (find(pack(a, 2)) == find(pack(b, 0)))
		return true;
	if (find(pack(a, 2)) == find(pack(b, 1)))
		return true;
	
	return false;
}

bool isn_2(int a, int b) // 是否(不)是 a 吃 b
{
	if (find(pack(a, 0)) == find(pack(b, 0)))
		return true;
	if (find(pack(a, 0)) == find(pack(b, 2)))
		return true;
	if (find(pack(a, 1)) == find(pack(b, 0)))
		return true;
	if (find(pack(a, 1)) == find(pack(b, 1)))
		return true;
	if (find(pack(a, 2)) == find(pack(b, 1)))
		return true;
	if (find(pack(a, 2)) == find(pack(b, 2)))
		return true;
	
	return false;
}

inline int read()
{
	int ch = getchar(), t = 1, n = 0;
	while (isspace(ch)) { ch = getchar(); }
	if (ch == '-') { t = -1, ch = getchar(); }
	while (isdigit(ch)) { n = n * 10 + ch - '0', ch = getchar(); }
	return n * t;
}

int main()
{
	int n = read(), k = read(), opt, ta, tb, ans = 0;

	for (int i = 0; i < n; i++) // 并查集初始化
	{
		ufs[pack(i, 0)] = pack(i, 0);
		ufs[pack(i, 1)] = pack(i, 1);
		ufs[pack(i, 2)] = pack(i, 2);
	}

	for (int i = 0; i < k; i++)
	{
		opt = read(), ta = read() - 1, tb = read() - 1;

		if (ta < 0 || ta >= n || tb < 0 || tb >= n) // 越界
		{
			ans++;
			continue;
		}

		if (opt & 1) // 同类标记
		{
			if (isn_1(ta, tb)) // 谎言
			{
				ans++;
				continue;
			}

			unite(pack(ta, 0), pack(tb, 0)); // 更新
			unite(pack(ta, 1), pack(tb, 1));
			unite(pack(ta, 2), pack(tb, 2));
		}
		else // 梅开二度
		{
			if (isn_2(ta, tb))
			{
				ans++;
				continue;
			}

			unite(pack(ta, 0), pack(tb, 1));
			unite(pack(ta, 1), pack(tb, 2));
			unite(pack(ta, 2), pack(tb, 0));
		}
	}

	printf("%d\n", ans); // 精准结束

	return 0;
}
posted @ 2020-08-05 22:15  5ab  阅读(180)  评论(0编辑  收藏  举报