POJ 1703 Find them, Catch them

题意 :有两个不同的帮派,每个帮派至少有一个人。 判断两个人是否属于同一个帮派。有 T 组测试数据,给你 N 个人,编号从 1 到 N,操作 M 次。每次操作输入一个字符和两个数 x ,y ,如果字符为 A 则判断 x 和 y 是否属于同一个帮派,并且输出结果。 如果字符为 D 则明确告诉你 x 和 y 是属于不同帮派的。

分析:并查集,以前接触的并查集都是让我们判断是否属于同一个连通分量,但这道题却让你判断是否属于同一类。

除了像普通的并查集定义一个 p[] 记录父亲节点外,还定义一个 r[] 记录当前点与其所属的连通分量的根节点的关系。
r[] = 0 表示属于同一个帮派; r[] = 1表示与其根节点属于不同的帮派。
开始时初始化自己是自己的父亲 p[x] = x,自己与自己属于同一类 r[x] = 0.一旦输入 D 断定 x 和 y 属于不同集合后,就连接 x 和 y 所在的树,同时更新 r[]一旦输入 A如果 find(x) 不等于 find(y) 说明还没有判断过 x 与 y 直接输出关系不确定即可Not sure yet.
如果find(x)等于 find(y) ,但是他们的r不等,说明属于不同帮派,输出In different gangs.如果他们的r相等,说明属于同一个帮派,则输出In the same gang

注意:1.find()函数寻找根节点的时候要不断的更新 r  
       根据子节点与父亲节点的关系和父节点与爷爷节点的关系,推导子节点与爷爷节点的关系
       如果 a 和 b 的关系是 r1, b 和 c 的关系是 r2,
       那么 a 和 c 的关系就是 (r1+r2)%2 .
PS:因为只用两种情况所以对 2 取模。
       如果实在不好理解,那么我们就枚举推理一下,共有 2*2 = 4种情况:
       (a, b) (b, c)  (a, c)  (r1+r2)%2
          0 0       0        0        a 和 b是同类 , b 和 c 是同类, 所以 a 和 c 也是同类
          0      1       1        1        a 和 b是同类 , b 和 c 是异类, 所以 a 和 c 也是异类
          1      0       1        1        a 和 b是异类 , b 和 c 是同类, 所以 a 和 c 是异类
          1      1       0        0        a 和 b是异类 , b 和 c 是异类, 所以 a 和 c 是同类
     2.Union()联合两棵树的时候也要更新两棵树的根的关系
       定义:fx 为 x的根节点, fy 为 y 的根节点
       联合时,使得 p[fx] = fy; 同时也要寻找 fx 与 fy 的关系。关系为:(r[x]+r[y]+1)%2
       如何证明?
       fx 与 x 的关系是 r[x], 
       x 与 y 的关系是 1 (因为确定是不同类,才联合的), 
       y 与 fy 关系是 r[y],模 2 是因为只有两种关系
       所以又上面的一点所推出的定理可以证明 fx 与 fy 的关系是: (r[x]+r[y]+1)%2

#include <cstdio>
#include <iostream>
#include <cstring>
#define MAXN 100050

using namespace std ;

int p[MAXN] ;  //存贮父节点
char r[MAXN] ; //存储与根节点的关系,0代表同类,1代表不同类

int
Find_Father ( int x )
{
    if ( x != p[x] )
    {
        int temp ;      //记录父节点,方便更新下面r[]
        temp = p[x] ;
        p[x] = Find_Father ( p[x] ) ;
        r[x] = ( r[x] + r[temp] ) % 2 ;    //根据子节点与父亲节点的关系和父节点与爷爷节点的关系,推导子节点与爷爷节点的关系
    }
    return p[x] ;
}

void
Union ( int const x , int const y )
{
    int pre_x , pre_y ;
    pre_x = Find_Father ( x ) ;
    pre_y = Find_Father ( y ) ;
    p[pre_x] = pre_y ;
    r[pre_x] = ( r[x] + r[y] + 1 ) % 2 ;   //pre_x与x关系 + x与y的关系 + y与pre_y的关系 = pre_x与pre_y的关系
    return ;
}

void
Set ( int const n )
{
    for ( int i = 0 ; i <= n ; i ++ )
    {
        p[i] = i ;     //自己是自己的父节点
        r[i] = 0 ;       //自己和自己属于同一类
    }
}

void
Solve ( )
{
    getchar ( ) ;   //吃掉回车
    char ch ;
    int x , y ;
    scanf ("%c%d%d" , & ch , & x , & y ) ;
    if ( 'A' == ch )
    {
        if ( Find_Father(x) == Find_Father(y ) ) //如果根节点相同,则表示能判断关系
        {
            if ( r[x] != r[y] )
            {
                printf ("In different gangs.\n") ;
            }
            else
            {
                printf ("In the same gang.\n") ;
            }
        }
        else
        {
            printf ("Not sure yet.\n") ;
        }
    }
    else
    {
        Union ( x , y ) ;
    }
    return ;
}

int
main ( )
{
    int T ;
    scanf ("%d" , & T ) ;
    while ( T-- )
    {
        int n , m ;
        scanf ("%d%d" , & n , & m ) ;
        Set ( n ) ;
        while ( m -- )
        {
            Solve ( ) ;
        }
    }
    return 0 ;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值