题目描述——洛谷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 1∼N 编号。每个动物都是 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 1≤N≤5×104, 1 ≤ K ≤ 1 0 5 1\le K \le 10^5 1≤K≤105。
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;
}