POJ 2912 Rochambeau(路径压缩并查集)

273 篇文章 0 订阅
112 篇文章 0 订阅

POJ 2912 Rochambeau(路径压缩并查集)

http://poj.org/problem?id=2912

题意:

        有n(n<=500)个小孩玩游戏,里面有一个人是裁判,剩下的人分为三组(一组固定出石头,一组固定出剪子,一组固定出布,但是可能有某组为空)。现在我们既不知道裁判是谁也不知道分组的情况。现在n个小孩玩剪刀石头布,已知每组的小孩只会出同一种手势,但是裁判出的手势是随机的,所以现在给出你M次两个小孩之间的游戏结果,问你是否能判断出谁是裁判,如果能的话就输出对应的信息。

分析:

        由于N<=500,所以我们依次假设第i个小孩是裁判,然后通过比赛的结果来判断第i个小孩是否可能为裁判。

        如何判断第i个小孩是不是裁判呢?

        假设第i个小孩是裁判,那么他出拳是随机的,所以只要有他参与的比赛我们自动忽略。然后剩下的两两比赛就是路基压缩并查集的一个个关系了,我们按照路径压缩并查集的做法看看剩下的比赛是否存在冲突。如果有冲突,那么i小孩不能是裁判。如果没冲突,i小孩能做裁判。

        经过N轮枚举,我们能得到共有X个小孩可以做裁判,如果:

        X==0,说明 Impossible

        X>1,说明Can not determine

        X==1时,输出那个裁判的编号 且输出其他所有小孩的第一条冲突发生语句的位置 最大值Y,即通过前Y条语句我们就能知道N-1个小孩都不可能是裁判(虽然此为Accept代码解法,但此处我个人认为有问题。因为就算你通过前Y条关系知道前N-1个小孩不可能是裁判,但是你还是需要看第N个小孩的枚举情况,看完第N个小孩的最后一条语句才能知道第N个小孩是否可以做裁判吧,说不定正好最后一条语句发生冲突呢?)

        枚举裁判,每个非裁判的小孩是并查集的一个节点。如果a->b==0,那么表示a与b同类。a->b==1,那么表示a吃b(比如a为剪子的时候,b为布)。a->b==2,那么表示a被b吃(比如a为布的时候,b为剪子)。

        剩下的就是类似食物链POJ1182的解法分析了。记得此时忽略你假设为裁判的所有语句。

AC代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int MAXN=500+100;
const int MAXM=2000+100;
int F[MAXN];//父节点
int v[MAXN];//v[i]=x表示节点i与其父节点的大小关系

int findset(int i)
{
    if(F[i]==-1)return i;
    int temp = findset(F[i]);
    v[i] += v[F[i]];
    v[i]%=3;
    return F[i]=temp;
}
void bind(int i,int j,int temp)
{
    int fa= findset(i);
    int fb= findset(j);
    if(fa!=fb)
    {
        F[fb]=fa;
        v[fb]=(v[i]-temp-v[j]+3*3)%3;
    }
}
struct command
{
    int a,temp,b;
} coms[MAXM];
bool judge[MAXN];//judge[i]=true 表示i可以作为裁判
int pos[MAXN];//保存在第几步可判断i是裁判
int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)==2)
    {

        for(int i=0; i<m; i++)
        {
            char c;
            scanf("%d%c%d",&coms[i].a,&c,&coms[i].b);
            if(c=='=')
                coms[i].temp=0;
            else if(c=='>')
                coms[i].temp=1;
            else if(c=='<')
                coms[i].temp=2;
        }

        memset(pos,-1,sizeof(pos));//pos[i]=x表示i作为裁判的时候,第x位置会出问题,如果x=-1则表示i左裁判不会出问题
        memset(judge,1,sizeof(judge));
        for(int i=0; i<n; i++) //假设i为裁判
        {
            memset(F,-1,sizeof(F));
            memset(v,0,sizeof(v));
            for(int j=0; j<m; j++)
            {
                int a,b,temp;
                a=coms[j].a;
                b=coms[j].b;
                temp=coms[j].temp;
                if( (a==i||b==i)  )//有关i的命令都跳过
                    continue;
                else//ab不含裁判i
                {
                    int fa=findset(a);
                    int fb=findset(b);
                    if(fa==fb)//同一分量
                    {
                        if( (v[b]+temp)%3!=v[a] )//不合法
                        {
                            judge[i]=false;
                            pos[i]=j+1;
                            //printf("pos[%d]=%d\n",i,j);
                            break;
                        }
                    }
                    else//不同连通分量
                    {
                        bind(a,b,temp);
                    }
                }
            }
        }
        int tot=0;//记录有多少个裁判
        int judge_i;
        int max_p=0;//所有出错裁判的最大错误位置
        for(int i=0; i<n; i++)
        {
            if(judge[i])//i可以作为裁判
            {
                tot++;
                judge_i=i;
            }
            else
                max_p=max(max_p,pos[i]);//找到所有出错裁判的最大错误位置
        }
        if(tot==0)
            printf("Impossible\n");
        else if(tot==1)
            printf("Player %d can be determined to be the judge after %d lines\n",judge_i,max_p);
        else if(tot>1)
            printf("Can not determine\n");
    }
    return 0;
}


评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值