注意事项:
尽量先看懂思路,代码其实真的很简单,没什么可说的
题目:
给定 n 堆石子,两位玩家轮流操作,每次操作可以从任意一堆石子中拿走任意数量的石子(可以拿完,但不能不拿),最后无法进行操作的人视为失败
问如果两人都采用最优策略,先手是否必胜
第一行包含整数 n
第二行包含 n 个数字,其中第 i 个数字表示第 i 堆石子的数量
如果先手方必胜,则输出 Yes
否则,输出 No
输入:
2
2 3
输出:
Yes
public class 博弈论_Nim游戏 {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
//将理论用在代码上很简单,就是将所有堆的石子数量异或一下,如果不为0就是先手必胜,为0就是先手必败
int res = 0;
while (n-- > 0) {
int x = in.nextInt();
res ^= x;
}
if (res != 0) System.out.println("Yes");
else System.out.println("No");
}
}
思路:
首先结论,Nim游戏存在先手必胜法
例如:有两堆石子,第一堆有2个,第二堆有3个,先手必胜
步骤:
1.先手从第二堆拿走1个,此时第一堆和第二堆数目相同
2.无论后手怎么拿,先手都在另外一堆石子中取走相同数量的石子即可
然后我们来了解两个名词:先手必胜状态和先手必败状态
先手必胜状态:先手操作完,可以让后手走到某一个必败状态:a1 ^ a2 ^ a3 ^ … ^ an = x ≠ 0
先手必败状态:先手操作完,后手走不到任何一个必败状态:a1 ^ a2 ^ a3 ^ … ^ an = x = 0
(^符号表示异或,是二进制位运算符,相同为0,不同为1)
三条证明:
1.根据游戏规则,我们可以得知当每一堆的石子数量都为0时:0 ^ 0 ^ 0 ^ … ^ 0 = 0,游戏结束
2.在操作过程中,如果 a1 ^ a2 ^ a3 ^ … ^ an = x ≠ 0那么玩家必然可以通过拿走某一堆若干个石子将异或结果变为0
证明:假设x的二进制中最高位的1在第k位,那么在a1,a2…an中,必然有一个数ai的第k位为1,且ai ^ x < ai, 那么从ai中拿走(ai - (ai ^ x))个石子,ai中还剩ai - (ai - (ai ^ x))个石子,而ai - (ai - (ai ^ x)) = ai ^ x, 那么当前公式就变为a1 ^ a2 ^ a3 ^ … ^ ai ^ x ^ … ^ an = x ^ x = 0
3.在操作过程中,如果a1 ^ a2 ^ a3 ^ … ^ an = x = 0,那么无论玩家怎么拿,必然会导致最终异或结果不为0
证明:这里就反证一下就可以,如果从ai堆中拿了石子之后记作ay,其他堆不变,异或结果还是为0,那么ai就一定等于ay,这就矛盾了
基于以上三条证明,可以得知:
1.如果先手面对的局面是a1 ^ a2 ^ a3 ^ … ^ an = x ≠ 0,那么先手总可以通过拿走某一堆若干个石子,将局面变成a1 ^ a2 ^ a3 ^ … ^ an = x = 0。如此重复,最后一定是后手面临最终没有石子可拿的状态。
先手必胜。
2.如果先手面对的局面是a1 ^ a2 ^ a3 ^ … ^ an = x = 0,那么无论先手怎么拿
都会将局面变成a1 ^ a2 ^ a3 ^ … ^ an = x ≠ 0,那么后手总可以通过拿走某一堆若干个石子,将局面变成a1 ^ a2 ^ a3 ^ … ^ an = x = 0。如此重复,最后一定是先手面临最终没有石子可拿的状态。
先手必败。
声明:
算法思路来源为y总,详细请见https://www.acwing.com/
本文仅用作学习记录和交流