翻转游戏 II

Submission

运行时间: 31 ms

内存: 16.1 MB

class Solution:
    def canWin(self, s: str) -> bool:
        def mex(vals):
            i = 0
            while i in vals:
                i += 1
            return i

        @cache
        def dfs(x):
            if x < 2:
                return 0
            vals = set()
            for a in range(x-1):
                b = x-a-2
                vals.add(dfs(a) ^ dfs(b))
            return mex(vals)

        plus = []
        l, r = 0, 0
        while r < len(s):
            if s[r] == '-':
                d = r-l
                if d >= 2:
                    plus.append(d)
                l = r + 1
            r += 1
        d = len(s) - l
        if d >= 2:
            plus.append(d)

        v = 0
        for x in plus:
            v^= dfs(x)
            
        return v > 0

# 作者:Firepaw
# 链接:https://leetcode.cn/problems/flip-game-ii/solutions/58448/game-theory-100-python3-by-firepaw/
# 来源:力扣(LeetCode)
# 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Explain

这个题解使用了博弈论中的 SG 定理。将初始的字符串 s 按照连续的 '+' 分割成多个子串,每个子串代表一个独立的游戏局面。对每个子串计算它的 SG 函数值,最后将所有 SG 函数值做异或操作,如果结果大于 0,则先手必胜,否则先手必败。 在计算单个子串的 SG 函数时,使用记忆化搜索 dfs(x),其中 x 表示当前子串的长度。搜索过程中,枚举所有可能的分割方式,将分割后的两部分递归计算 SG 函数,并做异或操作,得到一个 SG 函数值的集合。最后,用 mex 函数找到最小的不属于这个集合的非负整数,作为当前状态的 SG 函数值。

时间复杂度: O(n^3)

空间复杂度: O(n)

```python
class Solution:
    def canWin(self, s: str) -> bool:
        def mex(vals):
            i = 0
            while i in vals:
                i += 1
            return i

        @cache
        def dfs(x):
            if x < 2:
                return 0
            vals = set()
            for a in range(x-1):
                b = x-a-2
                vals.add(dfs(a) ^ dfs(b))
            return mex(vals)

        plus = []
        l, r = 0, 0
        while r < len(s):
            if s[r] == '-':
                d = r-l
                if d >= 2:
                    plus.append(d)
                l = r + 1
            r += 1
        d = len(s) - l
        if d >= 2:
            plus.append(d)

        v = 0
        for x in plus:
            v^= dfs(x)
            
        return v > 0
```

注释:
1. `mex` 函数用于计算 mex 值,即找到最小的不属于给定集合的非负整数。
2. `@cache` 装饰器用于记忆化搜索,避免重复计算。
3. `dfs(x)` 函数用于计算当前状态的 SG 函数值,其中 `x` 表示当前子串的长度。
4. 首先将初始字符串按照连续的 '+' 分割成多个子串,存储在 `plus` 数组中。
5. 对于每个子串,调用 `dfs(x)` 计算其 SG 函数值,并将所有 SG 函数值做异或操作,得到最终结果 `v`。
6. 如果 `v > 0`,则先手必胜,否则先手必败。

Explore

SG定理(Sprague-Grundy定理)是博弈论中的一个重要概念,用于解决可以分解为多个独立子游戏的组合游戏。基本原理是每个可分解的游戏状态都可以分配一个名为SG函数的值。SG函数的值是基于该状态下所有可能移动后的状态的SG值计算得出的。具体来说,SG函数值是这些后续状态的SG值所不能达到的最小非负整数(mex)。在翻转游戏中,每个连续的 '+' 子串被视为一个独立的游戏状态。SG定理应用于此游戏的方式是,如果从初始状态开始,所有独立子游戏的SG值的异或和非零,则先手有必胜策略;如果异或和为零,则先手必败。

在翻转游戏中,玩家需要翻转连续的两个 '+' 符号为 '-' 符号。因此,对于长度小于2的子串,即不包含连续两个 '+' 的子串,玩家无法执行任何有效的移动。因此,这样的子串被视为终止状态,其SG函数值为0。这表示该游戏状态是一个P位置(先手必败),因为先手玩家无法进行任何改变游戏结果的操作。

`mex`函数(Minimum EXcludant)的作用是找到一个集合中未出现的最小非负整数。工作原理是:首先从0开始逐个检查整数是否在集合中。这个过程是通过一个循环实现的,循环的索引从0开始,如果当前数字存在于集合中,则索引递增继续检查下一个数字;如果不存在,则这个数字就是mex值。这种方法保证了找到的是最小的缺失非负整数,因为它是按顺序从最小值开始检查的。

在翻转游戏中,玩家的操作是寻找连续的 '+' 并将其翻转为 '-'. 这意味着只有连续的 '+' 子串是可以被操作的独立单元。通过将字符串按照 '+' 的连续段进行分割,我们实际上是在定义游戏的独立子局面,每个子局面内部只包含连续的 '+'. 这种分割确实考虑了所有可能的翻转操作,因为任何有效的翻转操作都只能在至少两个连续 '+' 的子串内进行。这种方法有效地简化了问题,将复杂的字符串分解为一系列独立解决的小问题。