window.cnblogsConfig = { homeTopImg: [ "https://cdn.luogu.com.cn/upload/image_hosting/clcd8ydf.png", "https://cdn.luogu.com.cn/upload/image_hosting/clcd8ydf.png" ], }

博弈论

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\)。(学过位运算都知道吧

于是我们可以开始推导了:

  1. 首先明确我们的目标:目标是使得 \(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\)。这一步应该很好的就退出来了。
  2. 如果这个先手此时的异或和 = 0时:代表着此时的状态也是0,显然,没有东西取了,必输
  3. 如果这个先手此时的异或和 != 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";

【参考文献】:

posted @ 2023-12-15 20:41  gsczl71  阅读(34)  评论(1编辑  收藏  举报