并查集关系的传递(向量法, 算法的背后就是数学!)

两种关系的传递

先上题目链接 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WhereIsCodeFrom

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值