两种关系的传递
先上题目链接 poj 1703
思路:
对于A操作, 我们是判断a, b 的帮派关系, D操作我们可以确定a, 和 b 不是同一个帮派. 那么, 我们就使用 一个 r[] 数组来表示a, 节点与 他的祖宗节点的关系, 我们使用 0 表示在同一个帮派, 1 表示不在同一个帮派. 合并在一起后, 只要在同一颗树上的两个节点就能判断他们之间的关系(使用向量法), 注意在合并两个节点的时候我们是把a节点的祖宗节点连接到b的祖宗节点下, 那么我们就需要确定a的祖宗节点与b祖宗节点的关系, 重点来了(如何确定关系) 这里我们画图来表示关系. 首先我们有三个关系是知道的:
r1 : a 节点和 a祖宗的关系
r2 : b 和 b祖宗的关系
r3 : a 和 b 的关系
由向量的加减法可以得到r4 = r2 + r3 - r1;
其中 r1 = r[a], r[2] = r[b], r1 = 1 —> r[fa1] = r[b] + 1 - r[a]
在更新a 节点与 fa1(a祖宗) 节点关系的时候采用回溯累加对 2 取余 (这个是用穷举法得出要与2 取余的)
这一步可以在查找祖宗的时候顺便完成
int findSet(int x) {
if(x != s[x]) {
int t = s[x];
s[x] = findSet(s[x]);
r[x] = (r[x] + r[t]) & 1; // 回溯累加取余
}
return s[x];
}
我们得出r4 的关系其实还要做操作, 因为r[b] - r[a] + 1 可能大于 2 那么我们对 2 取余
那么合并可以得到
r[fa1] = (r[b] - r[a] + 1) & 1;
好, 那么 我们接下来就是解决在同一颗树上如何判断关系;
我们在判断是否在同一颗树上得时候已经更新了a, 与fa , b 与 fa 的关系(这个时候祖宗相同), 我相信很多人都没有想明白(这里也是用向量) 我放一个图
首先我们假设输入
D 1 2
D 2 3
D 1 4
那么我们构建的树的如下图
如果要判断树上两个点的关系 有一个规律 只要 两个点之间隔的节点数量为奇数那么 这两个点就是同一个帮派 (所以我们 % 2 的操作) 就是看两个点隔的节点数的奇偶情况, 所以我们与祖宗节点的更新 然后相减 就是 一个转换求向量的距离的过程, 又根据 0 在同一个帮派, 1 不同帮派, 可以将奇偶情况判断 转换为 与 祖宗的关系是否相等
所以 判断 r[a] 是否等于 r[b] 就能判断帮派关系
if(findSet(a) == findSet(b)) {
if(r[a] == r[b]) {
printf("In the same gang.\n");
} else {
printf("In different gangs.\n");
}
AC代码
#include<stdio.h>
#include<stdlib.h>
int s[100010], r[100010];
int n, m;
void initSet() {
for(int i = 1; i <= n; i++) {
s[i] = i;
r[i] = 0;
}
}
int findSet(int x) {
if(x != s[x]) {
int t = s[x];
s[x] = findSet(s[x]);
r[x] = (r[x] + r[t]) & 1;
}
return s[x];
}
void mergeSet(int a, int b) {
int afa = findSet(a);
int bfa = findSet(b);
s[afa] = bfa;
r[afa] = (r[b] - r[a] + 1) & 1;
}
int main() {
int t, a, b;
char ch;
scanf("%d", &t);
while(t--) {
scanf("%d%d", &n, &m);
initSet();
getchar();
for(int i = 0; i < m; i++) {
scanf("%c %d %d", &ch, &a, &b);
getchar();
if(ch == 'A') {
if(findSet(a) == findSet(b)) {
if(r[a] == r[b]) {
printf("In the same gang.\n");
} else {
printf("In different gangs.\n");
}
} else {
printf("Not sure yet.\n");
}
} else if(ch == 'D'){
mergeSet(a, b);
}
}
}
return 0;
}
三种关系的传递
题目链接poj 1182
Description
动物王国中有三类动物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
思路
首先我们确定三种关系
0 : a 和 b 同物种
1 : a 吃 b
2 : b 吃 a
为什么 因为 我们 输入1 表示 同 类 对应 1 - 1 = 0, 2 也是如此,
这里我直接给关系判别的式子
合并时:
r[fa1] = (3 + r[b] - r[a] + d - 1) % 3;
大家可能看不懂为什么是 + 3 , 因为 不加 3 的话 式子可能为 负数, 我们又不能根据负数判断关系, 这里可以根据我对于上一道题 求 r4 的关系 化 一下 当关系 为 负数 时 对应的是什么关系
那么我们如何判断该话 是 假话, 只要在同一颗树上就能判断真假,
那么我们可以画 一个 图 a 节点指向 fa b 指向 fa 我们是知道 a 与 fa 的关系 b 与 fa 的关系
a 与 b 的关系 我们也知道 , 那么我们自需要验证 (向量的减法)
(r[a] - r[b] + 3) % 3 != d - 1;
AC代码
#include<iostream>
int s[100010], r[100010];// 0 表示子与父节点为同一类, 1 表示 子吃父节点, 2 表示 子被父节点吃
int n, m;
void initSet() {
for(int i = 1; i <= n; i++) {
s[i] = i;
r[i] = 0;
}
}
int findSet(int x) {
if(x != s[x]) {
int t = s[x];
s[x] = findSet(s[x]);
r[x] = (r[x] + r[t]) % 3;
}
return s[x];
}
int main() {
scanf("%d %d", &n, &m);
int ans = 0, d, a, b; // 假话数目
initSet();
while(m--) {
scanf("%d%d%d", &d, &a, &b);
if(a > n || b > n || (d == 2 && a == b)) {
ans++;
continue;
} else {
int fa1 = findSet(a);
int fa2 = findSet(b);
if(fa1 == fa2) {
if((r[a] - r[b] + 3) % 3 != d - 1) {
ans++;
}
} else { // 合并操作
s[fa1] = fa2;
r[fa1] = (3 + r[b] - r[a] + d - 1) % 3;
}
}
}
printf("%d\n", ans);
return 0;
}