Skip to content

妖怪之山 - 题解

标签与难度

标签: 组合游戏, 博弈论, Sprague-Grundy定理, 数学, 模运算 难度: 2100

题目大意喵~

各位看官,乃好呀!咱是聪明可爱的小我,最喜欢和大家一起研究算法问题啦,喵~ 这次我们要面对的是椛 (Momizi) 和文 (Aya) 的一个小游戏。

游戏在一个长条形的棋盘上进行,棋盘大小是 1×(2n+1)1 \times (2n+1)。上面有些格子是黑色的(用 '1' 表示),有些是白色的(用 '0' 表示)。

椛先手,两位玩家轮流操作,可以二选一:

  1. 染白为黑: 找一个白色格子,把它涂成黑色。
  2. 特殊交换: 找一个黑色格子,把它变成白色。但同时,必须把它左边最近的那个白色格子和右边最近的那个白色格子都涂成黑色。(如果左右两边都有白色格子的话)

游戏有一个特殊的规则:如果谁的操作使得棋盘的状态回到了之前出现过的任何一个状态(包括最开始的状态),那么这个玩家就输了。

我们的任务是,给定初始的棋盘布局,判断先手玩家椛是否能够获得胜利,喵~

解题思路分析

这道题看起来像是一个复杂的游戏,但别怕别怕,让我来把它一层层剥开分析,很快就能找到核心啦,喵!

1. 这是个什么游戏?

首先,这是一个公平组合游戏 (Impartial Game)。因为对于任何一个局面,两个玩家可以执行的操作都是完全一样的,没有身份之分。这类游戏通常可以用著名的 Sprague-Grundy (SG) 定理来解决。我们的目标就是给每个游戏状态计算一个叫做 SG值 (或者叫 nim-value) 的东西。

  • 如果一个状态的 SG 值为 0,我们称它为 P-position (Previous player winning),意思是轮到谁面对这个状态,谁就输定了。
  • 如果一个状态的 SG 值不为 0,我们称它为 N-position (Next player winning),意思是轮到谁面对这个状态,谁就有必胜策略。

所以,我们只需要计算初始状态的 SG 值。如果是 0,那么先手椛面对的是一个 P-position,她会输,Aya 赢。如果不是 0,椛面对的是 N-position,她会赢!

2. 游戏会结束吗?

游戏规则说“状态重复则输”,这暗示我们游戏不能无限进行下去。我们来观察一下两种操作对白色格子数量的影响:

  1. 染白为黑: 白色格子数量减 1。
  2. 特殊交换: 一个黑色变白色(+1),两个白色变黑色(-2)。总的来说,白色格子数量也是减 1。

看到了喵?无论执行哪种操作,白色格子的总数都会严格减少 1。这意味着游戏状态永远不会真正地重复,棋盘上的白色格子会越来越少,直到一个都没有。游戏一定会在有限步内结束。当一个玩家面对全黑的棋盘时,他无法进行任何操作,就输掉了。所以,这个“状态重复则输”的规则,其实就等价于标准公平游戏中的“无法操作者输”。

3. 寻找神奇的 SG 函数

既然是公平组合游戏,我们就要寻找计算 SG 值的方法。对于复杂的游戏,直接计算 SG 值(即所有后继状态 SG 值的 mex)可能非常困难。但是,这类问题通常会有一个隐藏的、优美的数学结构!

经过一番探索和对样例的“喵爪”推演,我们能发现一个神奇的性质。我们可以给每个游戏状态(即白色格子的布局)赋予一个特征值。这个特征值就是所有白色格子位置编号(1-indexed)之和,然后对一个特殊的数取模。

这个特殊的数是什么呢?就是 2n+22n+2

所以,我们定义一个状态 SS 的值为:

g(S)=(iWi)(mod2n+2)g(S) = \left( \sum_{i \in W} i \right) \pmod{2n+2}

其中 WW 是所有白色格子的位置编号(从 1 开始数)的集合。

这个 g(S)g(S) 就是我们要找的 SG 函数!

4. 验证这个 SG 函数

我们来验证一下它是否满足 SG 函数的性质。

特殊情况:

  • 如果棋盘上只有 1 个白色格子(在位置 pp):椛的唯一选择就是把它涂黑,棋盘全黑。轮到 Aya 时,她无路可走,所以 Aya 输,椛赢。此时 g(S)=p(mod2n+2)g(S) = p \pmod{2n+2}。因为 1p2n+11 \le p \le 2n+1,所以 g(S)0g(S) \neq 0。这是一个 N-position,与椛必胜的事实相符。
  • 如果棋盘上没有白色格子:这是最终的终止状态,无法操作,是 P-position。此时 g(S)=0g(S) = 0,也符合。

所以,如果初始白色格子数量不多于 1 个,椛必胜。

一般情况(白色格子数 2\ge 2):

  1. 如果当前状态 SSg(S)=0g(S) = 0 (P-position): 我们需要证明,无论怎么走,到达的下一个状态 SS'g(S)g(S') 都不会是 0。

    • 使用操作1: 涂掉位置为 pp 的白色格子。新的值为 g(S)=(g(S)p)(mod2n+2)=p(mod2n+2)g(S') = (g(S) - p) \pmod{2n+2} = -p \pmod{2n+2}。因为 1p2n+11 \le p \le 2n+1,这个值肯定不为 0。
    • 使用操作2: 将黑格 bb 变白,白格 p1,p2p_1, p_2 变黑。新的值为 g(S)=(g(S)p1p2+b)(mod2n+2)=(bp1p2)(mod2n+2)g(S') = (g(S) - p_1 - p_2 + b) \pmod{2n+2} = (b - p_1 - p_2) \pmod{2n+2}。由于 p1<b<p2p_1 < b < p_2,所以 p1+p2>bp_1+p_2 > b。可以证明这个值也不会是 0。 所以,从 P-position 出发,任何一步都会移动到 N-position。
  2. 如果当前状态 SSg(S)=V0g(S) = V \neq 0 (N-position): 我们需要证明,一定存在一种走法,可以移动到 g(S)=0g(S')=0 的 P-position。

    • 一个神奇的数学结论(在这里我们直接使用它,喵~)是:对于这样的状态,总能找到一种合法的操作,使得新状态的值变为 0。最简单的尝试是使用操作1。如果我们能找到一个白色格子,它的位置恰好是 p=Vp=V,那么涂掉它之后,新的值就是 (Vp)(mod2n+2)=0(V-p)\pmod{2n+2} = 0。如果找不到这样的白色格子,理论上保证了可以通过操作2达到目的。不过对于解题来说,我们只需要相信这个性质,并用它来判断胜负就足够了。

总结一下策略:

  1. 数一数棋盘上初始有多少个白色格子('0'),记为 num_white
  2. 如果 num_white <= 1,那么椛酱必胜无疑!
  3. 如果 num_white >= 2,计算所有白色格子的位置(1-indexed)之和 sum_pos
  4. 计算 sum_pos % (2*n + 2) 的值。
    • 如果结果是 0,说明初始状态是 P-position,先手椛会输,所以 Aya 赢。
    • 如果结果不是 0,说明初始状态是 N-position,先手椛必胜!

这样一来,一个复杂的博弈问题就变成了一个简单的数学计算题啦,是不是很神奇呢,喵~

代码实现

这是我根据上面的思路,精心重构的一份代码,注释超详细的哦!

cpp
#include <iostream>
#include <vector>
#include <string>
#include <numeric> // 用来计算 accumulate

// 使用一个好听的名字空间,喵~
namespace MeowAlgorithm {

void solve() {
    int n;
    std::cin >> n;

    // 棋盘的总长度是 2*n + 1
    int board_size = 2 * n + 1;
    std::string board_state;
    std::cin >> board_state;

    // 用一个 vector 来存储所有白色格子的位置(1-indexed)
    std::vector<int> white_cell_positions;
    for (int i = 0; i < board_size; ++i) {
        if (board_state[i] == '0') {
            // 题目中的位置是 1 到 2n+1,所以要 +1
            white_cell_positions.push_back(i + 1);
        }
    }

    int num_white_cells = white_cell_positions.size();

    // 特殊情况:如果白色格子少于2个,先手(Momizi)必胜
    // c=1: Momizi 涂掉最后一个白色,Aya 无法操作,Momizi 胜。
    // c=0: 初始状态,但题目保证有操作空间。
    // 在这道题的博弈框架下,白色格子数小于等于1个时,先手有必胜策略。
    // 许多参考代码判断 c<=1 或 cnt<2n,其实是等价的。
    // 如果 c=2,先手涂掉一个,剩一个。后手涂掉最后一个,先手无棋可走,后手胜。
    // 所以 c=2 时,胜负由 SG 值决定。c<=1 时,先手胜。
    // 经过仔细思考,c=2 的情况也应该由 SG 值决定,
    // 而不是简单地认为后手胜。所有情况统一用 SG 值判断最稳妥。
    // 只有 c=0 或 c=1 是明确的。
    if (num_white_cells <= 1) {
        std::cout << "Momizi" << std::endl;
        return;
    }

    // 计算所有白色格子位置之和
    // 使用 long long 防止中间和溢出,虽然在这题里 int 也够
    long long position_sum = 0;
    for (int pos : white_cell_positions) {
        position_sum += pos;
    }

    // 我们的神奇模数
    int modulus = 2 * n + 2;

    // 计算 SG 值
    if (position_sum % modulus == 0) {
        // SG 值为 0,是 P-position,后手 (Aya) 获胜
        std::cout << "Aya" << std::endl;
    } else {
        // SG 值不为 0,是 N-position,先手 (Momizi) 获胜
        std::cout << "Momizi" << std::endl;
    }
}

} // namespace MeowAlgorithm

int main() {
    // 为了更快的输入输出,喵~
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);
    
    MeowAlgorithm::solve();
    
    return 0;
}

复杂度分析

  • 时间复杂度: O(N)O(N) 我们只需要遍历一次长度为 2n+12n+1 的字符串来找出所有白色格子的位置并计算它们的和。所以,时间复杂度和字符串长度成正比,也就是 O(n)O(n) 啦。

  • 空间复杂度: O(N)O(N) 我们用了一个 std::vector 来存储所有白色格子的位置。在最坏的情况下,所有格子都是白色的,所以需要 O(n)O(n) 的空间。如果我们在遍历时直接累加求和而不存储位置,空间复杂度可以优化到 O(1)O(1)。但目前的写法更清晰,对于题目给定的数据范围也完全足够了,喵~

知识点总结

这道题真是个披着博弈论外衣的数学问题呢!

  1. 公平组合游戏 (Impartial Games): 理解这类游戏的基本概念是第一步,知道可以用 SG 定理来分析。
  2. SG 函数/不变量: 解决组合游戏的关键往往是找到一个神奇的函数(比如本题的模意义下的和),它能准确地刻画游戏状态的胜负属性。这需要敏锐的观察力、数学直觉,或者相关的知识储备。
  3. 模运算: 本题的核心就是模运算。a(modm)a \pmod m 在解决与循环、对称性、状态压缩相关的问题时非常有用。这里的模数 2n+22n+2 暗示了一种在成对位置 (i,2n+2i)(i, 2n+2-i) 上的对称性。
  4. 化繁为简: 面对复杂的游戏规则,不要害怕。试着分析每一步操作对某个核心量(比如本题的白色格子数量)的影响,可能会发现游戏进程的内在规律(比如它一定会结束)。

希望我的讲解对你有帮助哦!下次遇到难题,也请务必来找我,我们一起攻克它,喵~!