poj-1703

//940K  469MS   C++
// cin TLE...
#include <cstdio>
#include <cstring>
#include <cassert>
#include <iostream>

using namespace std;

const int MAX = 100100;

int UF_set[MAX];

int hate[MAX];

int caseNum;
int criminalNum;
int messageNum;

void UF_getSetId(int curId) {
    if (UF_set[curId] == 0) {
        return;
    }
    int topId = UF_set[curId];
    while(UF_set[topId] != topId) {
        topId = UF_set[topId];
    }
    UF_set[curId] = topId;
}

void UF_fill(int A, int B) {
    UF_getSetId(A);
    UF_getSetId(B);
    int AsetId = UF_set[A];
    int BsetId = UF_set[B];

    if (!AsetId && !BsetId) {
        // A and B first filled, belong to different set and hate each other.
        UF_set[A] = A;
        UF_set[B] = B;
        hate[A] = B;
        hate[B] = A;
    } else if (AsetId && !BsetId) {
        // A has been filled, B not, B is added in the set which hated by A'set
        // also update hate[];
        int ASetHate = hate[AsetId];
        UF_set[B] = ASetHate;

        hate[A] = hate[AsetId];
        hate[B] = AsetId;
    } else if (!AsetId && BsetId) {
        // B has been filled, A not, A is added in the set which hated by B'set
        // also update hate[];
        int BSetHate = hate[BsetId];
        UF_set[A] = BSetHate;

        hate[A] = BsetId;
        hate[B] = hate[BsetId];
    } else if (AsetId && BsetId) {
        // A and B all filled before, should not in same set(becasue A and B hate each other)
        // assert(AsetId != BsetId);

        // get A's set's hate set, add B's set in this set.
        // and add B's set's hate set in A set.
        int ASetHate = hate[AsetId];
        if (ASetHate != BsetId) {

            int BSetHate = hate[BsetId];

            hate[BsetId] = AsetId;
            UF_set[BsetId] = ASetHate;
            hate[B] = AsetId;

            UF_set[BSetHate] = AsetId;
            hate[BSetHate] = ASetHate;

        }
    }
}

void ifSameGang(int A, int B) {
    UF_getSetId(A);
    UF_getSetId(B);
    int AsetId = UF_set[A];
    int BsetId = UF_set[B];
    // if A or B never filled
    if (!AsetId || !BsetId) {
        printf("Not sure yet.\n");
    } else if (AsetId == BsetId) {
        printf("In the same gang.\n");
    } else {
        if (hate[AsetId] == BsetId) {
            printf("In different gangs.\n");
        } else {
            printf("Not sure yet.\n");
        }
    }
}

char message[100];
int main() {
    scanf("%d", &caseNum);
    for (int i = 1; i <= caseNum; i++) {
        memset(UF_set, 0, sizeof(UF_set));
        memset(hate, 0, sizeof(hate));
        scanf("%d %d", &criminalNum, &messageNum);
        for (int i = 1; i <= messageNum; i++) {
            int A;
            int B;
            // cin>>message>>A>>B;
            scanf("%s", message);
            if (message[0] == 'D') {
                scanf("%d %d", &A, &B);
                // printf("%c %d %d\n", message, A, B);
                UF_fill(A, B);
            } else if (message[0] == 'A') {
                scanf("%d %d", &A, &B);
                // printf("%c %d %d\n", message, A, B);
                ifSameGang(A, B);
            }
        }
    }
}

一道并查集的中级应用题, 这道题的最大难点是,对于给定的一组输入, 除了知道两者是对立的以外,并不知道谁到底属于哪一方,

A与B对立,但是A和B的归属会有两种情况:

A属于dragon, B属于snake

A属于snake, B属于dragon,

用并查集显然无法表示这种模棱两可的情况,因此这种情况下,就不急着将A和B直接归类到dragon/snake. 而是令其现自立门派,分别属于自己的gang,即并查集中

A和B分别是两个set,且setId分别是A和B,

具体的操作例子:

当前加入了

A <->B, 

C<->D,

那么现在set的情况是: 一共有4个set,set的根分别就是  A,B,C,D,接下来加入新的对立关系,共有这些case:

case1: E<->F, E 和 F和之前加入的A B C D都没有关系,那么继续E和F建立自己的帮派, 一共有6个set了。

case2: A <->E, 因为已经知道A和B是对立的,那么E和B必然是一个帮派的,那么就将E加入到B的帮派中。

case3: B<->C, 因为知道 B和A对立,那么C必然和A一个帮派,那么将C加入到A帮派中, 而D和C对立,也就是和AC帮派对立,那么D一定和B一个帮派,将D加入到B帮派中。

就这样操作,

最后会会得到一些集合(因为题目并不保证最后一定会将子帮派彻底合并成dragon和snake, 比如,最后可能就是 A <->B. C<->D这个四个帮派),并且还要维护一个对立列表hate来记录对于某个帮派,其对立帮派是哪个,这样才能正确的判断,某两人是是对立还是不能确定。

在query对立关系的时候,如果A和B都属于一个帮派,那么好办,

如果不属于一个帮派,就需要检查所属于的两个帮派是否对立了.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值