跳到主要内容

1510. Perfect Stone Game

Stone Game IV

这次的动态规划题 和往常的DP题稍微有点不一样

多少需要用到一些数学概念 不是完全0数学

奇数VS.偶数

从题目叙述 我们能注意到 只要当前上场的玩家

拿走一把共计 某非零完全平方数 的石头

使得台面上没剩下任何石头 这个玩家就赢了

因为Alice又是老样子 照惯例先攻

于是若初始的石头总数能写成 奇数个非零完全平方数的和

那么Alice就能赢 反之她必输无疑

状态转移方程

从上面的观察 我们已经注意到

对于任何一个迭代到的剩馀石子数量ii

我们仅需要问:ii可否拆成奇数个非零完全平方数的和

而问题的解答 写成状态转移方程便是 对于1i1 \leq iiNi \in N

squares_counts[i]={1,if i is a perfect square itself1,if perfect_square[1,i) s.t. squares_counts[iperfect_square]=00,otherwisesquares\_counts[i] = \begin{cases} 1, & \text{if i is a perfect square itself} \\ 1, & \text{if } \exists \, perfect\_square \in [1, i) \text{ s.t. } squares\_counts[i - perfect\_square] = 0 \\ 0, & \text{otherwise} \end{cases}

大括号中 第一和第三行非常显而易见 关键在

第二行的理念:squares_counts[iperfect_square]=0squares\_counts[i - perfect\_square] = 0

说明iperfect_squarei - perfect\_square颗石子 只能写成偶数个非零完全平方数的和

而再加上抽走perfect_squareperfect\_square颗石子的这步操作

使得ii颗石子只能写成奇数个非零完全平方数的和 Alice必胜条件

于是我们拿到能写代码的状态转移方程啰👌

def judge_winnability(stones_count: int) -> bool:
squares_counts = [0] * (stones_count + 1)

for num in range(1, stones_count + 1):
max_sqrt = int(num**0.5)
if max_sqrt**2 == num:
squares_counts[num] += 1 # From 0 to 1.
continue

for sqrt in range(max_sqrt, 0, -1):
residual = num - (sqrt**2)
if squares_counts[residual] == 0:
squares_counts[num] += 1 # From 0 to 1.
break

return squares_counts[-1] == 1

Bottom Up_Efficiency 时间复杂度比较微妙 是O(nn)O(n\sqrt{n}) 其中nn是初始石子数量

每次迭代到的石子数量ii 都要检查所有i\leq i的非零完全平方数

空间复杂度就比较简单 是O(n)O(n)