题解:T189 小F的棋子移动 | 豆包MarsCode AI刷题

185 阅读5分钟

题目描述

小F和朋友在一个 n×mn×m 的棋盘上进行游戏,棋盘的初始状态是有一颗棋子放置在左上角 (1,1) 的位置。小F每次可以向右或向上移动棋子,移动的步数必须为奇数单位。当棋子无法继续移动到棋盘内的有效区域时,该玩家输掉游戏。小F先手出发,问你是否小F可以必胜。

例如:当棋盘的大小为 1×4 时,小F第一次可以向右移动 3 个单位,之后对手无法继续操作,小F获胜。如果胜利,那么输出 Yes,否则输出 No。

题目说的有点问题,这里应该是向右或者向下。

测试样例

样例1:

输入:n = 1 ,m = 1
输出:'No'

样例2:

输入:n = 1 ,m = 4
输出:'Yes'

样例3:

输入:n = 4 ,m = 1
输出:'Yes'

样例4:

输入:n = 4 ,m = 4
输出:'No'

题目分析:

这题很明显是一道类似博弈论的题目,突破点在于每次移动的步数必须是奇数,其实这里就是在引导我们奇偶性这一方向去思考,而事实的确如此,是否必胜就是由棋盘长宽的奇偶性决定的。小F是否能赢则取决于到达终点的最后一步是不是他走的,是则胜,不是则败。

解法1:模拟

我们可以先考虑比较简单的边界情况:

  1. 当棋盘大小为 1×1 时,小F无法移动,必败。
  2. 当棋盘大小为 1×n 时,小F是否必胜则取决于n的奇偶性,n为奇数则小F可以一步走到位,必胜;n为偶数则小F必败,因为不管怎么走,小F都不可能一步走到终点,但是小F走完后对手可以一步走完剩下的路程(奇+奇=偶)。
  3. 当棋盘大小为 m×1 时,情况跟2一样,只是棋子移动的方向换了一下。

下面我们讨论更加一般的情况,对于大小为 mxn 的棋盘,m和n的奇偶性搭配一共四种情况:

  • m为奇,n为奇
  • m为奇,n为偶
  • m为偶,n为奇
  • m为偶,n为偶

我们将棋子移动过程拆分成行移动和列移动两部分考虑,这里我们统一先走行再走列:

  1. m为奇,n为奇时,走完前面m行的过程必然是 小F -> 对手 -> .... -> 小F,这是由奇数性质决定的,一个奇数只能拆分成奇数个奇数的和,所以小F先手就意味着到达第m行的最后一步(当然最后一步也可以是第一步,也就是小F一步走到头)必然是由他走。但是此时剩下的n列刚好是奇数步,对手可以直接走完,所以小F必败。

  2. m为奇,n为偶时,小F只需要第一步走完m行,剩下的n列随便走都能赢。因为对手不可能一步走完n列(步数限定为奇数步),并且对手走完后剩下的必然是奇数步(偶=奇+奇),小F必然可以一步到位,小F必胜。

  3. m为偶,n为奇时,分析同情况1,走完前面m行的过程必然是 小F -> 对手 -> .... -> 对手,因为一个偶数只能拆分成偶数个奇数的和,所以小F先手就意味着到达第m行的最后一步肯定是对手走的,而剩下的n列刚好是奇数步,小F可以直接走完,必胜。

  4. m为偶,n为偶时,分析类似情况2和情况3,只是把两个情况的偶数走法拼到了一起。行列均为偶数步就意味着走完m行和n列的最后一步都是由对手走出的,因此小F必败。

综合上面的分析,我们的代码结构就很明显了:

def solution(n: int, m: int) -> str:
    # 奇+偶必胜,偶+奇必胜,其余必败
    if n % 2 == 0 and m % 2 == 0 or n % 2 != 0 and m % 2 != 0:
        return "No"
    return "Yes"

解法2:DP

这题除了模拟之外还可以用DP来解决,我们定义一个大小为 nxm 的 dp 数组,dp[i][j] 代表从棋盘起点到(i, j)小F是否必胜,dp[i][j]=True 代表必胜。因此我们求解的目标就变成了 dp[n][m]

关键是怎么确定状态转移方程?

我们还是考虑每一步的移动是怎么来的:对于点(i, j),它只有可能从水平和竖直两个方向移动过来,并且只能是通过奇数步移动过来。假设(i, j)可以通过(i - step, j)这个点移动得到[step%2 !=0],那么 dp[i][j] 的结果就取决于 dp[i - step][j],如果 dp[i - step][j]=False ,就说明从起点到(i - step, j)小F必败,这也意味着到终点的最后一步是对手走的。但是把终点延伸到(i, j)后,最后一步就变成小F来走了,因此小F就变成了必胜的局面。上面分析的是从竖直方向转移的结果,从水平方向转移的分析也是一样的。因此,我们状态转移方程的伪代码如下:

dp[i][j] = True if dp[i-step][j] or dp[i][j-step] = Fasle ( step%2 != 0 )

完整代码如下:

def solution(n: int, m: int) -> str:
    # 初始化dp数组
    dp = [[False] * (m + 1) for _ in range(n + 1)]
    
    # 边界情况
    if n == 1 and m == 1:
        return "No"
    
    # 动态规划填充dp数组
    for i in range(1, n + 1):
        for j in range(1, m + 1):
            # 如果当前位置是起点,直接标记为False
            if i == 1 and j == 1:
                dp[i][j] = False
            else:
                # 如果 dp[i-step][j] 或者 dp[i][j-step] = Fasle,说明在 (i-step, j) 或者
                # (i, j-step) 处小F必败,但是只要小F可以多走一步他就赢了,因此 dp[i][j] = True
                for step in range(1, i + 1, 2):
                    if (i - step > 0 and not dp[i - step][j]):
                        dp[i][j] = True
                        break # 我们只需要一条必胜路径即可
                for step in range(1, j + 1, 2):
                    if (j - step > 0 and not dp[i][j - step]):
                        dp[i][j] = True
                        break
    
    # 返回结果
    return "Yes" if dp[n][m] else "No"