数据结构并查集2 --种类并查集

前置学习:

种类并查集

种类并查集是拓展并查集的一种应用。普通并查集主要解决的是朋友的朋友是朋友的一类问题。而种类并查集则要解决敌人的敌人是朋友这样的一类问题。解决复杂的关系。

实现

多一个种族就开一倍的空间就可以了。
如敌人的敌人是朋友那么就只有2个种族就可以1-n表示一类,n-2n表示另一个类。
不同类中就是敌人同一个类就是朋友

简单来说就是有多少类就开多少倍的空间就可以了。

例题

P1892 [BOI2003]团伙

题目描述

现在有 n n n 个人,他们之间有两种关系:朋友和敌人。我们知道:

  • 一个人的朋友的朋友是朋友
  • 一个人的敌人的敌人是朋友

现在要对这些人进行组团。两个人在一个团体内当且仅当这两个人是朋友。请求出这些人中最多可能有的团体数。

输入格式

第一行输入一个整数 n n n 代表人数。

第二行输入一个整数 m m m 表示接下来要列出 m m m 个关系。

接下来 m m m 行,每行一个字符 o p t opt opt 和两个整数 p , q p,q p,q,分别代表关系(朋友或敌人),有关系的两个人之中的第一个人和第二个人。其中 o p t opt opt 有两种可能:

  • 如果 o p t opt optF,则表明 p p p q q q 是朋友。
  • 如果 o p t opt optE,则表明 p p p q q q 是敌人。

输出格式

一行一个整数代表最多的团体数。

样例 #1

样例输入 #1

6
4
E 1 4
F 3 5
F 4 6
E 1 2

样例输出 #1

3

提示

对于 100 % 100\% 100% 的数据, 2 ≤ n ≤ 1000 2 \le n \le 1000 2n1000 1 ≤ m ≤ 5000 1 \le m \le 5000 1m5000 1 ≤ p , q ≤ n 1 \le p,q \le n 1p,qn

题解

同类很好理解就用并查集就可以解决。
那么敌人的敌人怎么解决呢。
我们在开一倍空间来代表敌人。

如果 a 和 b 是敌人那么:

  • 让 a+n 指向 b
  • 让 b+n 指向 a

当在来一次的时候就会出现(如 a 和 c 是敌人)

  • a+n 和 c 合并 ,这时 c就和b一个集合了
  • c+n 指向 a

java代码

package com.yu;

import java.io.*;
import java.util.stream.IntStream;

public class Main {
    static final StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));

    public static String next() throws IOException {
        st.nextToken();
        return st.sval;
    }

    public static int nextInt() throws IOException {
        st.nextToken();
        return (int) st.nval;
    }

    public static int[] a;

    public static int find(int i) {
        if (a[i] == i) return i;
        else return a[i] = find(a[i]);
    }

    public static void union(int i, int j) {
        int x = find(i);
        int y = find(j);
        if (x != y) a[x] = y;
    }

    public static void main(String[] args) throws IOException {
        int n = nextInt();
        int k = nextInt();
        a = IntStream.range(0, 2 * n + 2).toArray();
        for (int i = 0; i < k; i++) {
            String opt = next();
            int p = nextInt();
            int q = nextInt();
            if ("E".equals(opt)) {
                union(q + n, p);
                union(p + n, q);
            } else {
                union(p, q);
            }
        }

        int ans = 0;
        for (int i = 1; i <= n; i++) {
            if (find(i) == i) ans++;
        }
        System.out.println(ans);

    }
}

[NOI2001] 食物链

题目描述

动物王国中有三类动物 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 1N 编号。每个动物都是 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 1N5×104 1 ≤ K ≤ 1 0 5 1\le K \le 10^5 1K105

题解

这个有ABC三个种类,所以我们想要开 3 ∗ n 3*n 3n 的空间。
我们可以令 1 ∗ n 1*n 1n 空间为本, 2 ∗ n 2*n 2n 空间为食物(被吃), 3 ∗ n 3*n 3n 空间为天敌(吃他的)
所以对于x来说。
[x]所在集合就是x的同类
[x+n]所在集合就是x的食物
[x+2*n] 所在集合就是x的天敌。

操作来说
如果 x x x y y y 那么

  • x 的食物是 y
  • x 的天敌是 y 的食物
  • y 的天敌是 x

如果 x x x y y y 的同类,那么

  • x 的食物不是 y
  • x 的天敌也不是 y

java实现代码

package com.yu;

import java.io.*;
import java.util.stream.IntStream;

public class Main {
    static final StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));

    public static int nextInt() throws IOException {
        st.nextToken();
        return (int) st.nval;
    }

    public static int[] a;

    public static int find(int i) {
        if (a[i] == i) return i;
        else return a[i] = find(a[i]);
    }

    public static void union(int i, int j) {
        int x = find(i);
        int y = find(j);
        if (x != y) a[x] = y;
    }

    public static void main(String[] args) throws IOException {
        int n = nextInt();
        int k = nextInt();
        a = IntStream.range(0, n * 3 + 3).toArray();
        int ans = 0;
        for (int i = 0; i < k; i++) {
            int j = nextInt();
            int x = nextInt();
            int y = nextInt();
            if (x > n || y > n) {
                ans++;
                continue;
            }
            if (j == 1) {
                if (find(x) == find(y + n) || find(x) == find(y + 2 * n)) ans++;
                else {
                    union(x, y);
                    union(x + n, y + n);
                    union(x + 2 * n, y + 2 * n);
                }
            } else {
                if (find(x) == find(y) || find(x) == find(y + n) || x == y) ans++;
                else {
                    union(x, y + 2 * n);
                    union(x + n, y);
                    union(y + n, x + 2 * n);
                }

            }
        }
        System.out.println(ans);
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

一只小余

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

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

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

打赏作者

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

抵扣说明:

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

余额充值