【ACWing】258. 石头剪子布

题目地址:

https://www.acwing.com/problem/content/description/260/

N N N个小朋友(编号为 0 , 1 , 2 , … , N − 1 0,1,2,…,N−1 0,1,2,,N1)一起玩石头剪子布游戏。其中一人为裁判,其余的人被分为三个组(有可能有一些组是空的),第一个组的小朋友只能出石头,第二个组的小朋友只能出剪子,第三个组的小朋友只能出布,而裁判可以使用任意手势。你不知道谁是裁判,也不知道小朋友们是怎么分组的。然后,孩子们开始玩游戏,游戏一共进行 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 1N500
0 ≤ M ≤ 2000 0≤M≤2000 0M2000

思路是带权并查集。可以分别枚举 0 , 1 , . . . , N − 1 0,1,...,N-1 0,1,...,N1号小朋友是裁判的情形,假设当前在枚举第 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 max1inti轮的时候,不能成为裁判的小朋友都可以被排除掉了,那么能确定裁判的轮数就是 max ⁡ 1 ≤ i ≤ n t i \max_{1\le i\le n} t_i max1inti

接下来考虑怎么用带权并查集维护两两的输赢关系。这里并查集维护的等价关系是“能确定输赢关系”,也就是说,如果 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)

猜你喜欢

转载自blog.csdn.net/qq_46105170/article/details/121760273