题目链接 :点击查看
题目描述 :
动物王国中有三类动物 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,以一个空格分隔。
以下 K 行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中 D 表示说法的种类。
若 D=1,则表示 X 和 Y 是同类。
若 D=2,则表示 X 吃 Y。
输出
只有一个整数,表示假话的数目。
输入输出样例 :
输入
100 7
1 101 1
2 1 2
2 2 3
2 3 3
1 1 3
2 3 1
1 5 5
输出
3
题目分析 :
本题同样用并查集来做,而且用到的是维护与根节点距离的并查集。由题意可知,对于编号x与编号y的动物,一共有3种关系,1 . x 吃 y 2 . x被y 吃 3.x 与 y是同类。在并查集的树形节点中,我们可以这样抽象出 x 与 y 的关系 : 假定y为根节点,如果x与y的距离为1(即相连中间没有节点) 则表示x吃y ,如果x与y的距离为2(中间相隔一个节点,两段相连),则表示x被y吃,如果x与y的距离为3,表示x与y是同类。向下递推,如果x与y的距离为4,可以看成x距y的同类的距离为1,x吃y的同类,也就是x吃y……我们可以引申为这样的规律,如果x与y的距离mod 3 == 1,则x吃y,如果x与y的距离mod 3 == 2,表示x被y吃,如果x与y的距离mod 3 == 0,表示x与y是同类。类比于前面我们做过的题,find函数通过路径压缩可以求得某个点(比如说x)的根节点,在find函数中用一个中间遍历t记录递归中的p[x](要是跟之前相同直接用p[x] = find(p[x]),在回溯过程p[x]始终等于根节点),同时,我们定义了一个数组d来记录每个点到父节点的距离。在递归回溯过程中,由于递归后的语句回溯过程会发生自内向外遍历,所以我们用d[x] += d[p[x]]可以从根节点的下一个点开始,依次加和每个点距离其父节点的距离,最后d[x]的值即为点x到其根节点的距离。这样,我们可以完成对于带权并查集的构建。在对假话进行判断时,条件二很容易判断,即判断输入的两个数(编号)x与y是否大于n即可,若是有一个大于n则这句话就是假话。而一、三条件判定则相对较麻烦,尤其是判断是否与前面的话冲突,这里我们用px 与 py分别算出x,y分别属于的集合编号(px = find(x), py = find(b)),如果px == py则他们属于同一个集合,如果描述的是他们是同一类(t == 1),则求(d[y] - d[x]) % 3的值,如果是0则表明确实是同一类,其他值则不是。为啥呢?我们由上面的分析可知,三个距离为一循环,每个相应循化中的值,性质是一样的。如果如果描述的是x吃y的关系(t == 2),则求(d[y] - d[x]) %3 是否等于1,若y与x相距一个单位,则x吃y,这个可以参考我们上述的递推过程。若是px != py那该怎么办?这就表明x 与 y之前没有任何语句建立过他们俩的关系,因此我们可以认为当前这句话就是对的,要按照这句话来构建x与y的关系。如果t == 1表明x与y同类,首先我们要合并两者所在的区间,p[px] = p[py],集合合并后以根节点py为最终的祖宗元素,此时之前x集合的根节点px距离根节点py的距离未知,而d[x]还是x距离px的距离,但根据二者同类可知,(d[px] + d[x] -d[y])mod 3 是否 == 0,即d[px] = d[y] - d[x]。同理要是t == 2,(d[px] + d[x] - d[y]) mod 3 是否 == 1 ,转换为 d[px] = d[y] + 1 - d[x]。上述就是判断假话的全过程,用res记录假话数量,进行m次即可。详见如下代码。
代码 :
#include<iostream>
#include<cstdio>
using namespace std;
const int N = 5e5 + 7;
int n, m;
int p[N], d[N];//p数组记录每个节点的父节点(根节点的父节点就是其本身) d数组记录的是每个节点到其父节点的距离(初始时每个点的父节点就是其本身,所以初始化为0)
int find(int x) {
if (p[x] != x) {
int t = find(p[x]);//先用临时变量来记录x的祖宗节点
d[x] += d[p[x]];//d[x]来记录x到其祖宗节点的距离,上述递归回溯时,会将x与父节点,x的父节点与x的父节点的父节点等等等各段的距离相加,最后总和就是x
p[x] = t; //递归后的语句是由内到外开始执行的,若此句放于递归语句前,则无法计算x到祖宗元素的距离
} //这里是路径压缩,x节点最终指向根节点
return p[x];
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i ++ ) p[i] = i;//初始化集合
int res = 0;//res记录假话
while (m -- ) {
int t, x, y;
cin >> t >> x >> y;
if (x > n || y > n) res ++ ;//不符合条件二
else {
int px = find(x), py = find(y);//px,py分别记录x与y的祖宗节点 即所在的集合编号
if (t == 1) {//t等于1时,表示x与y是同类
if (px == py && (d[x] - d[y]) % 3) res ++ ;//d[x] 与 d[y] 都是 x,y到根节点之间的距离
else if (px != py) {//不在同一集合中,这就暗示了之前并没有语句表明x与y之间的关系,此时这句话就是对的,然后我们按照x与y是同类来建立关系
p[px] = py;//集合合并
d[px] = d[y] - d[x];//求px到其父节点py的距离
}
}
else {//t == 2的情况,表示x吃y,即判断是否(d[x]- d[y]) % 3 == 1 即判断是否 (d[x]- d[y] - 1) % 3 == 0
if (px == py && (d[x] - d[y] -1) % 3 ) res ++ ;
else if (px != py) {//不在同一集合中,两者之前并无关系,也就是这句话表示的两者关系就是对的
p[px] = py;
d[px] = d[y] + 1 - d[x]; //d数组中值改变由此开始
}
}
}
}
cout << res;
return 0;
}
------------------------------------------------------------------------------
在此我们给出带权并查集的相关模板
维护到祖宗节点距离的并查集:
int p[N], d[N];
//p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离
// 返回x的祖宗节点
int find(int x)
{
if (p[x] != x)
{
int u = find(p[x]);
d[x] += d[p[x]];
p[x] = u;
}
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ )
{
p[i] = i;
d[i] = 0;
}
// 合并a和b所在的两个集合:
p[find(a)] = find(b);
d[find(a)] = distance; // 根据具体问题,初始化find(a)的偏移量