UVA 10158 并查集的经典应用

这个题目一看就是用并查集,有N个国家代表,在M行给出两两之间的关系,敌人或者朋友,(当然如果该关系跟已知关系冲突,则输出-1)

关系的几个约束条件时这样的

在朋友方面,朋友的朋友就是自己的朋友,这个就是并查集。

在敌人方面,

x和其所有朋友的敌人都是敌人。

x和其所有敌人的敌人都是朋友。

主要是这个敌人的状态不太好表示,不是一个并查集能做到的,我一开始犯糊涂,直接用个图把x的敌人存贮起来,但是因为每次交新的朋友或者敌人,就要搜索全图,而且要把自己的敌人圈更新到整个朋友圈,这样不仅难以实现,复杂度也是相当高

后来就发现一个神级方法,简单易用,即,每个国家都有自己的对立面(实际上不存在),作用是这样的,x的对立面为x+n,如果某国y要跟x做敌人,则,y就和x+n放在同一个并查集里。这样就不会跟本体有影响,但是又达到了结仇的目的。

这样的话,x和y结盟,则 x和y属于一个集合, x+n和y+n属于同一集合(把对立面也绑定,再结仇的时候无论是和x还是y结仇,都会同时跟两个国家结仇,这样就其实就达到了第一个条件)。

如果x和y结仇,则x和y+n在同一集合,同时,y和x+n在同一集合。(结仇之后,如果两个国家有共同的仇人,则通过并查集操作,必定到了同一个集合,这样就满足了仇人的仇人是朋友的条件)

这样判断前后是否冲突,也可以根据这几个条件,如果当前操作是结仇,则一旦发现x和y已经是一个集合(或者他们的对立面),则冲突

如果当前是结盟,一旦发现x和y+n是一个集合  或者 y和x+n是一个集合,则冲突。

#include <iostream>
#include <cstdio>
#define N 10010
using namespace std;
int f[2*N];
int n,x,y,c;
void init()
{
    for (int i=0;i<=2*n;i++){
        f[i]=i;
    }
}
int findset(int a)
{
    if (a!=f[a])
        f[a]=findset(f[a]);
    return f[a];
}

void solve()
{
    int r1,r2,r3,r4;
    r1=findset(x);
    r2=findset(y);
    r3=findset(x+n);
    r4=findset(y+n);
    //cout<<r1<<" "<<r2<<" "<<r3<<" "<<r4<<endl;
    if (c==1)
    {
        if (r1==r4){
            puts("-1");
            return;
        }
        f[r1]=r2;
        f[r3]=r4;
        return;
    }
    if (c==2)
    {
        if (r1==r2)
        {
            puts("-1");
            return;
        }
        f[r1]=r4;
        f[r2]=r3;
        return;
    }
    if (c==3)
    {
        if (r1==r2)
            puts("1");
        else
            puts("0");
    }
    if(c==4)
    {
        if (r1==r4)
            puts("1");
        else
            puts("0");
    }

}
int main()
{
    int i,j;
    scanf("%d",&n);
    init();
    while (scanf("%d%d%d",&c,&x,&y))
    {
        if (!c) break;
        solve();
    }
    return 0;
}
原文地址:https://www.cnblogs.com/kkrisen/p/3527173.html