文章是转载的,第一次遇见,记录一下
来自 https://leetcode-cn.com/problems/nim-game/solution/li-jie-bo-yi-wen-ti-zhong-bi-sheng-tai-he-bi-bai-t/
刷题中常见的博弈问题,本质就是先手通过一系列操作,进来把当前状态变成对后手不利的状态。
由于选手足够聪明和规则巧妙设计,先手在给出的状态下总是必胜或者必败的。
必败态和必胜态的定义:
必胜态:如果一个状态的后继状态中存在必败态,那么这种该状态下,先手必胜。
必败态:如果一个状态的所有后继状态都是必胜态,那么这种状态下,先手必败。
不难发现这是一个递归的定义~
题目一定会给出一个最终状态(因为只有当游戏是收敛的,才有有结果),选手无论如何抉择也一定会到达这个最终状态。比如本题中,把石子都拿走的状态。
这个最终状态要么为胜利,要么为失败。这个最终状态就是上面递归定义的终点~
以本题为例, 必胜态,必败态,最终状态,及状态转移路径如下图示:
根据上面的推到关系,我们可以写出如下代码:
class Solution {
public:
bool canWinNim(int n) {
vector<bool> state(n+1);
state[0] = false;
for(int i = 1; i <= n; i++) {
state[i] = false;
for(int j = 1; j <= 3 && i-j >= 0 && state[i] == false; j++) {
state[i] = !state[i-j];
}
}
return state[n];
}
};
But,Time Limit Exceeded …
再回到必胜态的定义,必胜态的后继状态中只要要有一个必败态才可以。因为本题中一次可以取一到三块石子。故一个必败态 i,可以导致 i+1,i+2,i+3 为必胜态,而 i + 4 肯定是必败态。
又因为 0 是最小的必败态,这样我们可以得出一个结论,当 n%4 == 0 时,先手必败,否则先手必胜。
class Solution {
public:
bool canWinNim(int n) {
return n%4 != 0;
}
};