You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3.4 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

##AcWing 1319. 移棋子游戏

一、题目描述

给定一个有 N 个节点的 有向无环图,图中某些节点上有棋子,两名玩家交替移动棋子。

玩家每一步可将任意一颗棋子沿一条有向边移动到另一个点,无法移动者输掉游戏。

对于给定的图和棋子初始位置,双方都会采取最优的行动,询问先手必胜还是先手必败。

输入格式 第一行,三个整数 N,M,KN 表示图中节点总数,M 表示图中边的条数,K 表示棋子的个数。

接下来 M 行,每行两个整数 X,Y 表示有一条边从点 X 出发指向点 Y

接下来一行, K 个空格间隔的整数,表示初始时,棋子所在的节点编号。

节点编号从 1N

输出格式 若先手胜,输出 win,否则输出 lose

数据范围 1≤N≤2000,1≤M≤6000,1≤K≤N

输入样例:

6 8 4
2 1
2 4
1 4
1 5
4 5
1 3
3 5
3 6
1 2 4 6

输出样例:

win

二、解题思路

首先定义 mex 函数,这是施加于一个集合的函数,返回 最小的不属于这个集合的非负整数

例:mex({1,2})=0,mex({0,1})=2,mex({0,1,2,4})=3

在一张有向无环图中,对于每个点 u,设其 所有能到的点SG 函数值集合为集合 A,那么 uSG 函数值为 mex(A),记做 SG(u)=mex(A)

如图:

例图解释: SG(5)=mex({\phi})=0 SG(3)=mex({SG(5)})=mex({0})=1 SG(4)=mex({SG(5),SG(3)})=mex({0,1})=2 SG(2)=mex({SG(3)})=mex({1})=0 SG(1)=mex({SG(2),SG(4)})=mex({0,2})=1

本题思路

  • 如果只有一个棋子(棋子位置是s 先手必胜 \Leftrightarrow sg(s)!=0

  • 存在多个棋子(其实可以看成存在多个相同的棋盘,棋子的位置是s_1,…,s_k 先手必胜 \Leftrightarrow sg(s1)^sg(s2)^...^sg(sk) != 0

三、实现代码

#include <bits/stdc++.h>

using namespace std;
const int N = 2010, M = 6010;

// SG函数模板题
int n, m, k;
int f[N];

int h[N], e[M], ne[M], idx;
void add(int a, int b) {
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

int sg(int u) {
    //记忆化搜索
    if (~f[u]) return f[u];

    //找出当前结点u的所有出边看看哪个sg值没有使用过
    set<int> S;
    for (int i = h[u]; ~i; i = ne[i])
        S.insert(sg(e[i]));

    //找到第一个没有出现的过的自然数, 0,1,2,3,4,...
    for (int i = 0;; i++)
        if (S.count(i) == 0) {
            f[u] = i;
            break;
        }
    return f[u];
}

int main() {
    memset(h, -1, sizeof h);
    cin >> n >> m >> k;
    while (m--) {
        int a, b;
        cin >> a >> b;
        add(a, b);
    }

    memset(f, -1, sizeof f); //初始化sg函数的结果表
    int res = 0;
    while (k--) {
        int u;
        cin >> u;
        res ^= sg(u); //计算每个出发点的sg(u),然后异或在一起
    }

    if (res) //所有出发点的异或和不等于0,先手必胜
        puts("win");
    else //所有出发点的异或和等于0先手必败
        puts("lose");

    return 0;
}