动物王国中有三类动物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句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
1) 当前的话与前面的某些真的话冲突,就是假话;
2) 当前的话中X或Y比N大,就是假话;
3) 当前的话表示X吃X,就是假话。
你的任务是根据给定的N(1 <= N <= 50,000)和K句话(0 <= K <= 100,000),输出假话的总数。
Input
第一行是两个整数N和K,以一个空格分隔。
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。
若D=1,则表示X和Y是同类。
若D=2,则表示X吃Y。
Output
只有一个整数,表示假话的数目。
Sample Input
100 7 1 101 1 2 1 2 2 2 3 2 3 3 1 1 3 2 3 1 1 5 5
Sample Output
3
参考博客:https://www.cnblogs.com/haoabcd2010/p/5688902.html
参考博客:https://blog.csdn.net/c0de4fun/article/details/7318642(看过这个博客以后再去看第一个博客会帮助理解很多)
我将两篇博客总结如下:
PART 1 权值 r[i] 的确定
我们根据题意,森林中有3种动物。A吃B,B吃C,C吃A。
我们还要使用并查集,那么,我们就以动物之间的关系来作为并查集每个节点的
权值。
注意,我们不知道所给的动物(题目说了,输入只给编号)所属的种类。
所以,我们可以用动物之间“相对”的关系来确定一个并查集。
0 - 这个节点与它的父节点是同类
1 - 这个节点被它的父节点吃
2 - 这个节点吃它的父节点。
注意,这个0,1,2所代表的意义不是随便制定的,我们看题目中的要求。
说话的时候,第一个数字(下文中,设为d)指定了后面两种动物的关系:
1 - X与Y同类
2 - X吃Y
我们注意到,当 d = 1的时候,( d - 1 ) = 0,也就是我们制定的意义
当 d = 2的时候,( d - 1 ) = 1,代表Y被X吃,也是我们指定的意义。
所以,这个0,1,2不是随便选的
设 :f[i] 表示 i 号动物的父节点的编号 , r[N]是对父节点的关系 : 0 同类,1 被父节点吃,2 吃父节点
Part II - 路径压缩,以及节点间关系确定
确定了权值之后,我们要确定有关的操作。
我们把所有的动物全初始化。
for(int i = 1; i <= n ; i++)
{
f[i] = i; // 初始化父亲为自己
r[i] = 0; // 自己与自己的关系是同类
}
(1)路径压缩时的节点算法
我们设A,B,C动物集合如下:(为了以后便于举例)
A = { 1 , 2 , 3 ,4 ,5 }
B = { 6 , 7 , 8 ,9 ,10}
C = { 11, 12, 13,14,15}
假如我们已经有了一个并查集合
SET1 = {1,2},我们规定集合中第一个元素为并查集的“代表”
假如现在有语句:
2 2 6
这是一句真话 2吃6
我们根据这条语句赋值 即2是6的父亲
f[6] = 2 ; r[6] = 1 ;
那么此时和2在一个并查集里的1与6的关系如何呢
f[2] = 1 ; r[2] = 0;
我们可以发现6与1的关系是 1. (2吃6,2与1是同类,所以1也吃6)
通过穷举我们可以发现:
r[f[x]] r[x]
父亲对爷爷 儿子对父亲 儿子对爷爷
0 0 0
0 1 1
0 2 2
1 0 1
1 1 2
1 2 0
2 0 2
2 1 0
2 2 1
所以,儿子对爷爷的关系就可以推出这个式子 (r[x] + r[f[x]] ) % 3
用这个式子就解决了压缩路径的关系转换
对应代码:
int Find(int x) // 寻找x的根节点
{
int fx = x;
if(x != f[x])
{
fx = Find(f[x]);//先更新父节点
r[x] = (r[x] + r[f[x]])%3; // 更新为跟爷爷的关系
f[x] = fx;
}
return f[x];
}
这段代码就是先压缩到跟爷爷的关系,再找爷爷的爸爸,然后压缩到与爷爷的爸爸的关系......
通过递归一直找下去并且压缩,直至压缩至与根节点的关系
(2) 集合间关系的确定
在初始化的时候,我们看到,每个集合都是一个元素,就是他本身。
这时候,每个集合都是自洽的(集合中每个元素都不违反题目的规定)
注意,我们使用并查集的目的就是尽量的把路径压缩,使之高度尽量矮
假设我们已经有三个并查集集合
set1 = {1,2,7,10}
set2 = {11,4,8,13} 每个编号所属的物种见上文
set3 = {12,5,4,9}
如果给你一句话 d x y,你该如何判断是否真假呢
有两种情况
1)Find(x) = Find(y) , 即x,y在同一个并查集中, 只需判断关系是否矛盾即可。
如果 (3 - r[x] + r[y]) % 3 != d-1 代表假话
解释如下:
r[y] 是 y 与其根节点的关系 :根 — y
r[x] 是 x 与其根节点的关系: 根 — x
( 3 - r[x] ) %3 = 根据 x 与 根节点 的关系,逆推 根节点 与 x 的关系 :x — 根
这部分也是穷举法推出来的,我们举例:
x (以x为节点) r[x]
0 ( 3 - 0 ) % 3 = 0
1(父吃子) ( 3 - 1 ) % 3 = 2 //父吃子
2 (子吃父) ( 3 - 2 ) % 3 = 1 //子吃父
所以根据 x — 根 , 根 — y 以 y 为 儿子 , 根 为 爸爸 , x 为爷爷
再根据上文退出的 儿子对爷爷的关系: (r[x] + r[f[x]] ) % 3
可以得出:(3 - r[x] + r[y] ) % 3 即 x 与 y 的 关系 ,若与d - 1不相符即为假话
例如: 1 2 7
判断:Find(2) = Find(7)
then : r[2] = 0 (2和1是同类); r[7] = 1(7被1吃)
then : (3 - 0 + 1 ) % 3 = 1 != d-1
7被2吃,并不是同类 所以 1 2 7 是假话
2)Find(x) != Find(y)
此时x与y不在一个集合中,我们需要先把两个集合合并到一个集合中
如果直接:
int fx = Find(x);
int fy = Find(y);
if(fx != fy)
{
f[fy] = fx;
}
就是把Y所在集合的根节点的父亲设置成X所在集合的根节点
但是,Y所在集合的根结点与X所在集合的根节点的关系 ,要怎么确定呢?
我们设X,Y集合都是路径压缩过的,高度只有2层
先给出 根y 与 根x 关系更新公式:
r[y] = ( 3 - r[y] + ( d - 1 ) + r[x]) % 3;
这个公式,是分三部分,这么推出来的
第一部分: ( d - 1 ) 表示 : y 与 x 的关系 即 x — y
第二部分: 3 - r[y] 表示 : 根y 与 y 的关系 即 y — 根y
第三部分: r[x] 表示 : x 与 根x 的关系 即 根x — x
我们的过程是这样的:
先通过 ( ( 3 - r[y] + ( d - 1 ) ) % 3 得到 根y 与 x 的关系 即 x — 根y
再通过( ( 3 - r[y] + ( d - 1 ) ) % 3 + r[x] ) %3 得到 根y 与 根x 的关系
根据同余定理,最终得到 根y 与 根x 关系的更新式:
r[y] = ( 3 - r[y] + ( d - 1 ) + r[x]) % 3;
最终合并集合的核心代码为:
int fx = Find(x);
int fy = Find(y);
if(fx != fy)
{
f[fy] = fx;
r[fy] = (3 - r[y] + d-1 + r[x])% 3;
}
最终代码:
#include
#include
#include
#include
#include