Nim 游戏
甲,乙两个人玩 nim 取石子游戏。
nim 游戏的规则是这样的:地上有 \(n\) 堆石子(每堆石子数量小于 \(10^4\)),每人每次可从任意一堆石子里取出任意多枚石子扔掉,可以取完,不能不取。每次只能从一堆里取。最后没石子可取的人就输了。假如甲是先手,且告诉你这 \(n\) 堆石子的数量,他想知道是否存在先手必胜的策略。
这其实是一道很典型的题目:P2197 【模板】Nim 游戏
先抛出定理:(\(\oplus\)代表异或)
Nim 和 = \(a_1 \oplus a_2 \oplus a_3 \oplus \cdots \oplus a_n\)
如果 Nim 和为 \(0\) 的时候,先手必败,否则先手必胜
其实理论是很抽象的。
为什么异或就可以算出先手必胜或者必败呢?
在证明之前不妨先知道几个定理:
- 异或这个位运算支持交换律和结合律,所以可以
交换着乱搞 - 先手必胜等价于后手必输,后手必胜等价于先手必输。
- 如果没有后继状态的状态必然是必败状态。(意思大概就是:如果后面的状态是先手必输,那么前面这个接着的状态就是先手必赢)
- 如果 \(a_1 \oplus x = k\) ,那么 \(a_1 \oplus x \oplus k = 0\)。
- 如果 \(a_1 \oplus a_2 \oplus a_3 \oplus \cdots \oplus a_n = k\),那么 \(a_1 \oplus a_2 \oplus a_3 \oplus \cdots \oplus a_n \oplus k = 0\)。(
学过位运算都知道吧
于是我们可以开始推导了:
- 首先明确我们的目标:目标是使得 \(a_1 = a_2 = a_3 = \cdot \cdot \cdot= a_n\) 也就是 \(a_1 \oplus a_2 \oplus a_3 \oplus \cdots \oplus a_n = 0\)。这一步应该很好的就退出来了。
- 如果这个先手此时的异或和 = 0时:代表着此时的状态也是0,显然,没有东西取了,必输
- 如果这个先手此时的异或和 != 0:那么有一种方案:可以根据上面的定理四使得下一个(即后手)是必输的,所以这个(先手)就是必胜的(是定理三)。
然后,就做完了!!
简单吗?简单。抽象吗?抽象。难吗?我学了一个晚上
如此简短的代码可以过掉一个绿题,很不应该。我学了一个晚上还不懂(现在懂了。。),只是绿题,不应该
cin >> n;
ans=0;
for(int i = 1;i <= n;i++){int x;cin>>x;ans^=x;}
if(ans) cout<<"Yes\n";
else cout<<"No\n";
【参考文献】:
- https://www.luogu.com.cn/blog/483037/qian-tan-bo-yi-lun
- https://www.luogu.com.cn/blog/efforts-will-pay-off/solution-p2197
- https://www.luogu.com.cn/blog/attack/bo-yi-lun-ru-men-zhi-nim-you-hu
- https://www.luogu.com.cn/blog/yby39/solution-p2197
- https://oiwiki.com/math/game-theory/impartial-game/
- https://www.luogu.com.cn/blog/aiyoupass/solution-p2197