题目链接:http://poj.org/problem?id=2912
题目:
题目大意:
n个人进行m轮剪刀石头布游戏(0<n<=500,0<=m<=2000)
接下来m行形如x,y,ch的输入,ch='='表示x,y平局, ch='>'表示x赢y,ch='<'表示x输y, 但是我们不知道x,y的手势是什么;
其中有一个人是裁判,它可以出任意手势,其余人手势相同的分一组,共分为三组,可以存 在空组。
也就是说除了裁判外,其余人每一次出的手 势都相同,问能不能确定裁判是几号,如果能,输出 最少在第几轮可以确定;
题解:
带权并查集
我们考虑枚举裁判,然后对每轮进行处理,包含当前枚举的裁判的轮我们直接跳过
二者之前的关系我们用0(=),1(>),2(<)表示,记得%3,用val数组记录当前点到所在并查集根节点的关系
对于每轮,我们首先判断是否在同一联通块里。如果在,判断当前关系是否符合,即val[b]是否等于val[a]+ch(我们定义ch是b连向a的),如果是就继续,不是的话说明当前的点不是裁判;如果不在,就带权合并一下就好了
那么最少的发现裁判的轮数呢?我们发现我们实际上在做一个排除法,那么我们最晚排除的那个人的轮数就是最少的发现裁判的轮数
#include<algorithm> #include<cstring> #include<cstdio> #include<iostream> using namespace std; const int N=500+15; const int M=2000+15; int n,m; int fa[N],a[M],b[M],ch[M],val[N]; int find(int x) { if (fa[x]==-1) return x; int tmp=find(fa[x]); val[x]+=val[fa[x]]; val[x]%=3; return fa[x]=tmp; } int main() { while (~scanf("%d%d",&n,&m)) { char c[10]; for (int i=1;i<=m;i++) { scanf("%s",c+1); a[i]=b[i]=0; int len=strlen(c+1); int l=1; while (c[l]>='0'&&c[l]<='9') a[i]=(a[i]<<3)+(a[i]<<1)+c[l]-'0',l++; if (c[l]=='=') ch[i]=0; if (c[l]=='>') ch[i]=1; if (c[l]=='<') ch[i]=2; l++; while (c[l]>='0'&&c[l]<='9'&&l<=len) b[i]=(b[i]<<3)+(b[i]<<1)+c[l]-'0',l++; //printf("%d %d %d ",a[i],ch[i],b[i]); } int round=0,judger=-1; bool mark=false; for (int i=0;i<n;i++) { bool flag=false; memset(fa,-1,sizeof(fa)); memset(val,0,sizeof(val)); for (int j=1;j<=m;j++) { if (a[j]==i||b[j]==i) continue; int fx=find(a[j]),fy=find(b[j]); if (fx==fy) { if (val[b[j]]!=(val[a[j]]+ch[j]+3)%3) { round=max(round,j); flag=true; break; } } else { fa[fy]=fx; val[fy]=(val[a[j]]+ch[j]-val[b[j]]+3)%3; } } if (!flag)//当前的是法官 { if (judger==-1) { judger=i; } else { mark=true; break; } } } if (mark) printf("Can not determine "); else if (judger==-1) printf("Impossible "); else printf("Player %d can be determined to be the judge after %d lines ",judger,round); } return 0; }