题目地址:
https://www.acwing.com/problem/content/description/260/
N N N个小朋友(编号为 0 , 1 , 2 , … , N − 1 0,1,2,…,N−1 0,1,2,…,N−1)一起玩石头剪子布游戏。其中一人为裁判,其余的人被分为三个组(有可能有一些组是空的),第一个组的小朋友只能出石头,第二个组的小朋友只能出剪子,第三个组的小朋友只能出布,而裁判可以使用任意手势。你不知道谁是裁判,也不知道小朋友们是怎么分组的。然后,孩子们开始玩游戏,游戏一共进行 M M M轮,每轮从 N N N个小朋友中选出两个小朋友进行猜拳。你将被告知两个小朋友猜拳的胜负结果,但是你不会被告知两个小朋友具体使用了哪种手势。比赛结束后,你能根据这些结果推断出裁判是谁吗?如果可以的话,你最早在第几轮可以找到裁判。
输入格式:
输入可能包含多组测试用例。每组测试用例第一行包含两个整数 N N N和 M M M。接下来 M M M行,每行包含两个整数 a , b a,b a,b,中间夹着一个符号 > , = , < >,=,< >,=,<,表示一轮猜拳的结果。两个整数为小朋友的编号, a > b a>b a>b表示 a a a赢了 b b b, a = b a=b a=b表示 a a a和 b b b平手, a < b a<b a<b表示 a a a输给了 b b b。
输出格式:
每组测试用例输出一行结果,如果找到裁判,且只能有一个人是裁判,则输出裁判编号和确定轮数。
如果找到裁判,但裁判的人选多于 1 1 1个,则输出Can not determine
。
如果根据输入推断的结果是必须没有裁判或者必须有多个裁判,则输出Impossible
。具体格式可参考样例。
数据范围:
1 ≤ N ≤ 500 1≤N≤500 1≤N≤500
0 ≤ M ≤ 2000 0≤M≤2000 0≤M≤2000
思路是带权并查集。可以分别枚举 0 , 1 , . . . , N − 1 0,1,...,N-1 0,1,...,N−1号小朋友是裁判的情形,假设当前在枚举第 x x x号小朋友是裁判的情况,然后遍历 M M M轮操作,如果某次操作的两个小朋友 a a a和 b b b其中一个是裁判,则此轮操作略过;否则的话,先看一下并查集是否能判定 a a a和 b b b是谁赢谁输,如果能,再看一下当前操作的结果,如果不符合,则说明 x x x不可能是裁判(因为如果 x x x是裁判,那么所有不含他的操作都应该合法),如果符合,则继续看下一次操作;如果当前并查集无法判断 a a a和 b b b是谁赢谁输,则将他们合并,并将边权赋值,使得无矛盾。最后看一下能当裁判的人的个数,如果有多于 1 1 1个人能当裁判,则输出Can not determine
;如果没有任何人能充当那个唯一的裁判,则输出Impossible
。如果有唯一的裁判,那么上面的枚举中必然存在唯一的小朋友,使得排除他的所有操作都合法,他的编号是很容易记录下来的。而为了找到确定他是裁判的轮数,我们可以开个变量 t x t_x tx,每次枚举第 x x x号小朋友是否可以是裁判的时候,看一下不包含他的操作是否有矛盾,如果有,则将 t x t_x tx记录为第一次产生矛盾的轮数 k k k,也就是说,到达第 k k k轮时,我们就已经能判断出 x x x不是裁判了;如果一直没有矛盾,可以令 t x = 0 t_x=0 tx=0。显然到达 max 1 ≤ i ≤ n t i \max_{1\le i\le n} t_i max1≤i≤nti轮的时候,不能成为裁判的小朋友都可以被排除掉了,那么能确定裁判的轮数就是 max 1 ≤ i ≤ n t i \max_{1\le i\le n} t_i max1≤i≤nti。
接下来考虑怎么用带权并查集维护两两的输赢关系。这里并查集维护的等价关系是“能确定输赢关系”,也就是说,如果 x x x和 y y y之间猜拳,根据已知的信息我们能判断胜负,那么这两个点就在同一个集合里。至于具体谁胜谁负,就需要对边进行赋权。不妨设 d [ x ] d[x] d[x]是 x x x和其父亲 p [ x ] p[x] p[x]的胜负关系,如果 x = p [ x ] x=p[x] x=p[x]则 d [ x ] = 0 d[x]=0 d[x]=0;如果 x < p [ x ] x<p[x] x<p[x]则 d [ x ] = 1 d[x]=1 d[x]=1;如果 x > p [ x ] x>p[x] x>p[x]则 d [ x ] = 2 d[x]=2 d[x]=2。容易知道, x x x与树根的胜负关系就等于 ( d [ x ] + d [ p [ x ] ] + . . . ) m o d 3 (d[x]+d[p[x]]+...)\mod 3 (d[x]+d[p[x]]+...)mod3。那么 x x x与 y y y的关系是类似的,只不过边要反向,胜负关系为 ( d [ x ] + d [ p [ x ] ] + . . . − d [ y ] − d [ p [ y ] ] − . . . ) m o d 3 (d[x]+d[p[x]]+...-d[y]-d[p[y]]-...)\mod 3 (d[x]+d[p[x]]+...−d[y]−d[p[y]]−...)mod3。根据这个可以求 x x x与 y y y的胜负关系,然后看情形做合并操作,来对 p [ p x ] p[px] p[px]赋值。代码如下:
#include <iostream>
using namespace std;
const int N = 510, M = 2010;
int n, m;
int p[N], d[N], x[M], y[M], comp[M];
void init() {
for (int i = 0; i < n; i++) {
p[i] = i;
d[i] = 0;
}
}
int find(int x) {
if (p[x] != x) {
int root = find(p[x]);
d[x] = (d[x] + d[p[x]]) % 3;
p[x] = root;
}
return p[x];
}
int main() {
while (scanf("%d%d", &n, &m) != -1) {
for (int i = 1; i <= m; i++) {
char ch;
scanf("%d%c%d", &x[i], &ch, &y[i]);
if (ch == '<') comp[i] = 1;
else if (ch == '=') comp[i] = 0;
else comp[i] = 2;
}
int cnt = 0, judge = 0, line = 0;
// 枚举k号小朋友做裁判的情形
for (int k = 0; k < n; k++) {
// 更新一下并查集
init();
// 遍历所有操作
for (int i = 1; i <= m; i++) {
int a = x[i], b = y[i], pa = find(a), pb = find(b);
// 略过含裁判的操作
if (a != k && b != k) {
// 如果pa等于pb,意味着a和b的关系可以确定,则看有没有矛盾;
// 否则合并两个集合,并对d[pa]赋值
if (pa == pb) {
if ((d[a] - d[b] + 3) % 3 != comp[i]) {
line = max(line, i);
break;
}
} else {
p[pa] = pb;
d[pa] = (d[b] - d[a] + comp[i] + 3) % 3;
}
}
// 一直没产生矛盾,则说明k是可以当唯一的裁判的,记录之
if (i == m) {
cnt++;
judge = k;
}
}
}
// 注意一个边界情况,如果没有操作,那么可能当唯一裁判的小朋友个数就等于总个数
if (!m) cnt = n;
if (cnt > 1) puts("Can not determine");
else if (!cnt) puts("Impossible");
else printf("Player %d can be determined to be the judge after %d lines\n", judge, line);
}
return 0;
}
每组数据时间复杂度 O ( N M ) O(NM) O(NM),空间 O ( N ) O(N) O(N)。