侦查帮派问题

一 问题描述

警察局决定从两个帮派青龙帮和白蛇帮开始治理混乱,首先需要确定犯罪分子属于哪个团伙。比如,有两名罪犯,他们是否属于同一帮派?警察必须根据不完整的信息做出判断。假设有 N 个罪犯,编号为 1 到 N,其中至少一人属于青龙帮,至少一人属于白蛇帮,请依次给出 M 个消息,消息类型有两种:D a b,表示 a 和 b 属于不同的帮派。A a b,表示查询 a 和 b  是否属于同一帮派。

二 输入和输出

1 输入

第 1 行包含单个整数,即测试用例的个数。每个测试用例都以两个整数 N 和 M 开始;接着是 M 行,每行都包含如上所述的一个信息。

2 输出

对每个查询操作,都根据之前获得的信息进行判断,答案可能是 In the same gangs、In the different gangs 和 Not sure yet,分别表示在同一帮派中、在不同帮派中和还不确定。

三 输入和输出样例

1 输入样例

1

5 5

A 1 2

D 1 2

A 1 2

D 2 4

A 1 4

2 输出样例

Not sure yet.

In different gangs.

In the same gang.

四 分析

可以用一个集合表示一个帮派,根据集合号判定是否属于同一帮派。在并查集的基本操作中,Union(x、y) 表示将 x 和 y 合并为同一集合。与并查集的集合合并不同,本问题需要将两者划分为不同的集合。

1 划分为不同集合的方法

可以给每个节点 x 都复制一个影子 x+n,将 x、y 划分到不同的集合,只需将 x 和 y 的影子(y+n)合并到一个集合,并将 x 的影子(x+n)和 y 合并为同一集合,执行 Union(x,y+n)、Union(x+n,y),表示将 x 和 y 划分到不同的集合。

2 判定是否属于同一集合

因为将 x 和 y 划分到不同集合,与彼此的影子进行了合并,即 x 和 y 的影子(y+n)集合号相同或者 x 的影子(x+n)和 y 集合号相等时,说明 x、y 属于不同的集合;而 x 和 y 集合号相等或者 x 的影子和 y 的影子集合号相等时,说明 x、y 属于同一集合;对于其他情况,不确定是否属于同一集合。

3 合并优化

若将高树合并到矮树之下,则合并后的树高增1;若将矮树合并到高树之下,则合并的树高不变。树高越大,查找祖宗时经过的节点越多,效率越低。因此采用启发式合并,将矮树合并到高树之下,若树的高度一样,则合并后树根的高度增1.

五 算法设计

1 初始化

将 n 扩大为 2n 个节点,初始化每个节点的集合号都为其自身高度,高度为0。

2 划分为不同的集合。

执行 Union(x,y+n)、Union(x+n,y)、表示 x、y 属于不同的集合。

3 判定是否属于同一集合

若 Find(y+n)==Find(x)||Find(x+n)==Find(y),则属于不同的集合;若(Find(x)==Find(y)||Find(x+n)==Find(y+n)),则属于同一集合;否则不确定是否属于同一集合。

六 图解

1 初始化

根据样例,一共有 5 个节点,将其扩大为 10 个节点。初始化每个节点的集合号为自身,高度 h 为 0。

2 A 1 2

查询 1 和 2 是否属于同一帮派。Find(1)=1,Find(6)=6,Find(2)=2,Find(7)=7,前两个判定条件都不满足,不确定 1 和 2 是否属于同一帮派。

3 D 1 2

将 1 和 2 划分到不同的集合。将 1 和 2+5 合并,将 1+5 和 2 合并。

4 A 1 2

查询 1 和 2 是否属于同一帮派。因为 (Find(2+5)==Find(1)|| Find(1+5)==Find(2)),所以 1 和 2 不属于不一帮派。

5 D 2 4

将 2 和 4 划分到不同的集合。和一将 2 和 4+5 进行合并,将 2+5 和 4 合并。将高度小的树合并到高度大的树下面,因此将 9 合并 到 2 下面,将 4 合并到 7 下面。

6 A 1 4

表示查询 1 和 4 是否属于同一帮派。因为 Find(1)==Find(4)|| Find(1+5)==Find(4+5),因此 1 和 4 属于同一帮派。

七 代码

package com.platform.modules.alg.alglib.poj1703;

public class Poj1703 {
    public String output = "";
    private final int MAXN = 200010; // 因为有影子数据量*2,不然会越界!!run time error!!!

    private int n;
    private int m;
    private int fa[] = new int[MAXN];
    private int h[] = new int[MAXN]; // h 用来区分树的高度,但其不存储树的具体高度


    void Init() {
        for (int i = 1; i <= 2 * n; i++) {
            fa[i] = i;
            h[i] = 0;
        }
    }

    int Find(int x) {
        if (x != fa[x])
            fa[x] = Find(fa[x]);
        return fa[x];
    }

    void Union(int x, int y) {
        int a = Find(x);
        int b = Find(y);
        if (a == b) return;
        if (h[a] > h[b]) //启发式合并,把矮树合并到高树之下
            fa[b] = a;
        else {
            fa[a] = b;
            if (h[a] == h[b])//若树的高度一样,合并后树根的树高+1
                h[b]++;
        }
    }

    public String cal(String input) {
        String[] line = input.split("\n");
        String[] words = line[0].split(" ");
        n = Integer.parseInt(words[0]);
        m = Integer.parseInt(words[1]);
        Init();
        for (int i = 0; i < m; i++) {
            String[] word = line[1 + i].split(" ");
            char ch[] = new char[2];
            ch[0] = word[0].charAt(0);
            int x = Integer.parseInt(word[1]);
            int y = Integer.parseInt(word[2]);
            if (ch[0] == 'D') {
                Union(x, y + n);
                Union(x + n, y);
            } else {
                if (Find(y + n) == Find(x) || Find(x + n) == Find(y))
                    output += "In different gangs.\n";
                else if (Find(x) == Find(y) || Find(x + n) == Find(y + n))
                    output += "In the same gang.\n";
                else
                    output += "Not sure yet.\n";
            }
        }
        return output;
    }
}

八 测试

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值