【洛谷】P2197【模板】Nim 游戏
洛谷专栏:模板,数学
洛谷模板:P2197 Nim 游戏
算法竞赛:数学,博弈论
题目链接:洛谷 P2197
题目描述
甲,乙两个人玩 nim 取石子游戏。
nim 游戏的规则是这样的:地上有 n n n 堆石子(每堆石子数量小于 10 4 10^4 104),每人每次可从任意一堆石子里取出任意多枚石子扔掉,可以取完,不能不取。每次只能从一堆里取。最后没石子可取的人就输了。假如甲是先手,且告诉你这 n n n 堆石子的数量,他想知道是否存在先手必胜的策略。
输入格式
本题有多组测试数据。第一行一个整数 T T T ( T ≤ 10 T\le10 T≤10),表示有 T T T 组数据
接下来每两行是一组数据,第一行一个整数 n n n,表示有 n n n 堆石子, n ≤ 10 4 n\le10^4 n≤104。
第二行有 n n n 个数,表示每一堆石子的数量.
输出格式共 T T T 行,每行表示如果对于这组数据存在先手必胜策略则输出
Yes
,否则输出No
。
Nim 游戏
Nim 游戏是博弈论经典模版题,代码非常简洁,但是合理的做法从零开始思考是比较困难的。这个游戏的变式也非常之多,本题算是博弈论的入门。
题目背景比较简单,这里不再赘述。这道模版题用到的两点结论,与异或运算基础知识可以参见此(附上两个链接)异或运算性质可以参见此相关,在这里先给出。
如果各堆中个数相异或起来为 0 0 0,则先手必输(对方选最优策略),即本题中的没有必赢策略。
如果各堆中个数相异或起来不为 0 0 0,则先手必赢(选最优策略),即本题中的有必赢策略。
这两个结论如何证明,这里我们用直观的思维和数学方法相辅地证明一下。(说明:在两方都一直选择最优策略的情况下有选择策略可使自己赢的称为必赢,没有策略使自己赢的称为必输,下面论述中包含许多以此作为默认条件的。"该操作一方"指要进行还未进行操作的一方)
-
这个游戏怎么算赢与输呢,根据题目来说,不能操作为输,既在该操作一方要操作时石子个数为零时,该操作一方为输,既对方为胜,那么也就是能使对方必输的,自己必赢;使对方必赢的,自己必输。
-
在这个过程中是不是每次双方操作后都保持与原来相同的状态呢(也就是必赢的还必赢,必输的还必输),如果是的话,那么也就是说从一开始就能确定结果。一开始石子个数相异或为零,那么最后相异或也为零,也就是没有石子了,代表着该操作的一方输了,那么我们上述的两点结论就是正确的了。【可实现证明的一种数学方法:第一归纳法。我们知道在石子数为零时,异或为零,该操作一方输;在中间操作过程中,操作一次后原来异或为零的现在还是异或为零,即可证明(其实就是状态保持不变)】那么我们现在只需要证明这个过程中,状态保持不变即可。
(一)
如果各堆中个数相异或起来为 0 0 0,且存在石子,那么在拿一次操作后,余下的个数相异或起来一定不为零。
如果 a 1 xor a 2 xor a 3 xor a i xor . . . xor a n = 0 a_1\operatorname{xor}a_2\operatorname{xor}a_3\operatorname{xor}a_i\operatorname{xor}...\operatorname{xor}a_n = 0 a1xora2xora3xoraixor...xoran=0 那么从一个数中去除一部分后的值一定不为 0 0 0,这是比较显然的,但也可以用反证法证明:如果存在 y > 0 y > 0 y>0 使 a 1 xor a 2 xor a 3 xor ( a i − y ) xor . . . xor a n = 0 a_1\operatorname{xor}a_2\operatorname{xor}a_3\operatorname{xor}(a_i - y)\operatorname{xor}...\operatorname{xor}a_n = 0 a1xora2xora3xor(ai−y)xor...xoran=0,那么 a 1 xor a 2 xor a 3 xor a i xor . . . xor a n = a 1 xor a 2 xor a 3 xor ( a i − y ) xor . . . xor a n a_1\operatorname{xor}a_2\operatorname{xor}a_3\operatorname{xor}a_i\operatorname{xor}...\operatorname{xor}a_n = a_1\operatorname{xor}a_2\operatorname{xor}a_3\operatorname{xor}(a_i - y)\operatorname{xor}...\operatorname{xor}a_n a1xora2xora3xoraixor...xoran=a1xora2xora3xor(ai−y)xor...xoran 即 a i = a i − y a_i = a_i - y ai=ai−y,则 y = 0 y = 0 y=0 与 y > 0 y > 0 y>0 相违背,不成立,即证明。
(二)
如果各堆中个数相异或起来不为 0 0 0,那么在拿一次操作后,可以使余下的个数相异或起来为零。
如果 a 1 xor a 2 xor a 3 xor a i xor . . . xor a n = x a_1\operatorname{xor}a_2\operatorname{xor}a_3\operatorname{xor}a_i\operatorname{xor}...\operatorname{xor}a_n = x a1xora2xora3xoraixor...xoran=x 那么假设 x x x 的二进制中的最高的 1 1 1 位为第 k k k 位,在 a 1 , a 2 , a 3 . . . a n a_1,a_2,a_3...a_n a1,a2,a3...an 中至少有一个数的第 k k k 位为 1 1 1,(可以根据异或的定义用反证法证明)假设这个数为 a i a_i ai,那么 a i xor x < a i a_i\operatorname{xor}x < a_i aixorx<ai(异或使第 k k k 位变成了零)由此说明 a i − ( a i xor x ) > 0 a_i - (a_i\operatorname{xor}x) > 0 ai−(aixorx)>0,那么我们取 a i − ( a i xor x ) a_i - (a_i\operatorname{xor}x) ai−(aixorx),原始式子也就变成了 a 1 xor a 2 xor a 3 xor ( a i − ( a i − ( a i xor x ) ) xor . . . xor a n = 0 a_1\operatorname{xor}a_2\operatorname{xor}a_3\operatorname{xor}(a_i - (a_i - (a_i\operatorname{xor}x))\operatorname{xor}...\operatorname{xor}a_n = 0 a1xora2xora3xor(ai−(ai−(aixorx))xor...xoran=0,即证明。
AC Code
cpp
#include <bits/stdc++.h>
using namespace std;
int main()
{
int t;
scanf("%d",&t);
while (t--)
{
int n,y=0,x;
scanf("%d",&n);
while (n--)
{
scanf("%d",&x);
y^=x;
}
if (y!=0) printf("Yes\n");
else printf("No\n");
}
return 0;
}
End
感谢观看,如有问题欢迎指出。
更新日志
- 2025/8/21 开始书写本篇 CSDN 博客,并完稿发布。
本篇博客最早由本人发布于洛谷文章广场,本篇博客对其进行了修改调整与完善丰富。