ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

891. Nim游戏

2021-10-11 09:32:13  阅读:186  来源: 互联网

标签:891 游戏 硬币 Nim 取子 异或 ...


题目传送门

一、Nim游戏介绍

给定\(n\)堆石子,两位玩家轮流操作,每次操作可以从任意一堆石子中拿走任意数量的石子(可以拿完,但不能不拿),最后无法进行操作的人视为失败。

问如果两人都采用最优策略,先手是否必胜。

据说,它源自中国,经由被贩卖到美洲的奴工们外传。辛苦的工人们,在工作闲暇之余,用石头玩游戏以排遣寂寞。后来流传到高级人士,则用便士(Pennies),在酒吧柜台上玩。

最有名的玩法,是把十二枚便士放成\(3、4、5\)三列,拿光铜板的人赢。

后来,大家发现,先取的人只要在\(3\)那列里取走\(2\)枚,变成了\(1、4、5\),就能稳操胜券了,游戏也就变得无趣了。

于是大家就增加列数,增加铜板的数量,这样就让人们有了毫无规律的感觉,不易于把握。

二、举个栗子

  • \(2\)和\(3\)两堆石子,甲先来,从\(3\)那堆中取\(1\)个,留下两堆,分别是\(2\)和\(2\)。

  • 以后不管乙怎么拿,甲都和他在相反的一堆中做同样的操作,这样,肯定是乙最先面对\(0\),\(0\)状态。

三、先上结论

在两名选手都足够聪明,每一步都是最优解的情况下:
\(a_1\) ^ \(a_2\) ^ ... ^\(a_n=0\) 先手必败
\(a_1\) ^ \(a_2\) ^ ... ^\(a_n\neq0\) 先手必胜
就是把所有堆的石子个数异或起来,结果是零,先手必败,不是零,先手必胜。

怎么样,是不是很神奇?我看到它的时候也觉得很神奇,完全没有道理的和异或运算扯上了关系。但这个定理的证明却也不复杂:

四、证明

分情况讨论:
(1)每一堆中的石子的个数都是零,不能再进行任何操作,没有可以拿的东西,就是输了,\(a_1\)$a_2$...^\(a_n\)=0 ^ 0 ... 0=0,此时当前操作人输了,如果是上来就是这种情况,那么先手必败,此时上面的Bouton结论是正确的。

(2)如果\(a_1\)$a_2$...^\(a_n=x \neq0\) ,一定可以通过拿走某一堆中的一些石子,把式子的异或值变成0。
证明:我们先找到x的二进制表示中最高的一位1,设是第k位。说明\(a_1\)~\(a_n\)中必然至少有一个数\(a_i\),\(a_i\)的第k位的值是1.(要不这个位置的1是咋来的?)
那么 $a_i $^ x \(< a_i\) (因为\(a_i\)的第k位是1,x的第k位也是1,这样两个数字异或,只有一种可能,就是变小),我们就可以在\(a_i\)中拿走$ a_i-a_i$ $x$个石子(这个值肯定是正的)。这时,$a_i$这堆石子的个数就变成了$a_i-(a_i-a_i$\(x)\)= \(a_i\) ^x个。
那么现在的异或结果就是:
\(\ \ \ \ \ \ \ a_1\)^\(a_2\) ...^\(a_i\) ^ \(x\).. ^\(a_n\)
\(\because\) \(a_1\)$a_2$...^\(a_i\) ...... ^\(a_n =x\) 代入上边的式子
\(\therefore\) $ x ^{\wedge} x=0$ 证毕,我们一定可以通过拿走一堆中的一些石子,达到\(a_1\)$a_2$...^\(a_n=0\) 的目标。

(3)如果\(a_1\)$a_2$...^\(a_n=0\)的情况,那么不管怎么去拿,拿完之后的所有数的异或值肯定不是零。

反证法
(假设从任意一堆里去拿大于零的石子个数,拿完后,异或值还是零,然后我们证明这样做是不对滴~):

假设 我们在第i堆里去拿,拿完后第i堆的石子个数为\(a_i^{\prime}\)。
则:
\(a_1\)^ \(a_2\)^ ...^ \(a_i^{\prime}\) ^ \(a_{i+1}\).. ^ \(a_n=0\)

\(a_1\) ^ \(a_2\) ^ ...^ \(a_i\) ^ \(a_{i+1}\).. ^\(a_n=0\)

左右等式两边直接做异或运算,因为异或运算满足消去率,最终成为:
\(a_i^{\prime}\) $ ^{\wedge} a_i=0\( 也就是\)a_i=$ \(a_i^{\prime}\),也就是啥也没拿!啥也不拿是不行的,因为我们说好了是拿走的,就证明了是假设不对,拿完后异或值肯定不是零!

所以,最开始的状态,
(1)如果所有元素的异或值是零(第3种情况),甲不管怎么拿,剩余所有元素的异或值都将不是零,就是转为第2种情况。
(2)如果所有元素的异或值不是零(第2种情况),甲一定能有一种办法去拿,使得剩余所有元素的异或值是零,就是转为第3种情况。

上面的两个状态不断的重复转换,最终将达到第1种情况,就是没有东西可以拿了。至于谁可以赢,其实就是两个原因:
(1)最开始时各堆石子的异或值。
(2)在面临异或值不是零的情况下,不犯错误,一直按必胜的方法操作,则获胜。

那么,这个游戏在面临异或值不是零的情况下,怎么样操作可以达到所有元素的异或值是零的呢?
(1)计算出所有元素的异或值x.
(2)找出x的二进制中最高位是1的位置k。
(3)找到所有堆中,数量值的二进制第k位也是1的那个(可能不只一个,随便找一个就行),比如是\(a_i\)
(4)在\(a_i\)中,拿走 \(a_i-a_i\)^\(x\)个。

五、Bouton是如何想到这个方法的

为了进一步理解Nim取子游戏,我们考查某些特殊情况。如果游戏开始时只有一堆硬币,游戏人I则通过取走所有的硬币而获胜。现在设有2堆硬币,且硬币数量分别为N1和N2。

游戏人取得胜利并不在于N1和N2的值具体是多少,而是取决于它们是否相等。设N1!=N2 ,游戏人I从大堆中取走的硬币使得两堆硬币数量相等,于是,游戏人I以后每次取子的数量与游戏人II相等而最终获胜。但是如果N1= N2,则:游戏人II只要按着游戏人I取子的数量在另一堆中取相等数量的硬币,最终获胜者将会是游戏人II。这样,两堆的取子获胜策略就已经找到了。

现在我们如何从两堆的取子策略扩展到任意堆数中呢?
首先来回忆一下,每个正整数都有对应的一个二进制数,例如:\(57_{(10)}=111001_{(2)}\),即:\(57_{(10)}=2^5+2^4+2^3+2^0\)。于是,我们可以认为每一堆硬币数由2的幂数的子堆组成。这样,含有57枚硬币大堆就能看成是分别由数量为\(2^5、2^4、2^3、2^0\)的各个子堆组成。

现在考虑各大堆大小分别为\(N_1,N_2,N3,...,N_k\)的一般的Nim取子游戏。将每一个数\(N_i\)表示为其二进制数(数的位数相等,不等时在前面补0):
\(N_1=a_s..a_1a_0\)
\(N_2=b_s..b_1b_0\)
\(.....\)
\(N_k=m_s..m_1m_0\)

如果每一种大小的子堆的个数都是偶数,我们就称Nim取子游戏是平衡的,而对应位相加是偶数的称为平衡位,否则称为非平衡位。因此,Nim取子游戏是平衡的,当且仅当:

\(a_s+b_s+...+m_s\) 是偶数
.....
\(a_1+b_1+...+m_1\) 是偶数
\(a_0+b_0+...+m_0\) 是偶数

于是,我们就能得出获胜策略:
游戏人I能够在非平衡取子游戏中取胜,而游戏人II能够在平衡的取子游戏中取胜。
我们以一个两堆硬币的Nim取子游戏作为试验。设游戏开始时游戏处于非平衡状态。这样,游戏人I就能通过一种取子方式使得他取子后留给游戏人II的是一个平衡状态下的游戏,接着无论游戏人II如何取子,再留给游戏人I的一定是一个非平衡状态游戏,如此反复进行,当游戏人II在最后一次平衡状态下取子后,游戏人I便能一次性取走所有的硬币而获胜。而如果游戏开始时游戏牌平衡状态,那根据上述方式取子,最终游戏人II能获胜。
下面应用此获胜策略来考虑4-堆的Nim取子游戏。其中各堆的大小分别为7,9,12,15枚硬币。用二进制表示各数分别为:0111,1001,1100和1111。于是可得到如下一表:

\(2^3=8\) \(2^2=4\) \(2^1=2\) \(2^0=1\)
大小为7的堆 0 1 1 1
大小为9的堆 1 0 0 1
大小为12的堆 1 1 0 0
大小为15的堆 1 1 1 1

由Nim取子游戏的平衡条件可知,此游戏是一个非平衡状态的取子游戏,因此,游戏人I在按获胜策略进行取子游戏下将一定能够取得最终的胜利。具体做法有多种,游戏人I可以从大小为12的堆中取走11枚硬币,使得游戏达到平衡(如下表),

\(2^3=8\) \(2^2=4\) \(2^1=2\) \(2^0=1\)
大小为7的堆 0 1 1 1
大小为9的堆 1 0 0 1
大小为12的堆 0 0 0 1
大小为15的堆 1 1 1 1

之后,无论游戏人II如何取子,游戏人I在取子后仍使得游戏达到平衡。
同样的道理,游戏人I也可以选择大小为9的堆并取走5枚硬币而剩下4枚,或者,游戏人I从大小为15的堆中取走13枚而留下2枚。
归根结底,Nim取子游戏的关键在于游戏开始时游戏处于何种状态(平衡或非平衡)和第一个游戏人是否能够按照取子游戏的获胜策略来进行游戏。

既然和异或、二进制扯上了关系,那么是如何计算需要从哪个拿,拿几个就是巴顿老师当时的唯一需要解决的问题了:
(1)计算每一堆硬币数量的异或和,就是x=7912^15=\(1101_{(2)}\)=13
(2)找到x的最前面一个是1的位,本例就是1101中最前面的那个1.
(3)在给定的硬币堆中,找到一个和上面步骤2位数也是1的数字,比如本例我们找到的是12.
(4)计算 \(a_i-a_i\)^ \(x=12-12\)$13$=$1100_{(2)}-1100_{(2)}$\(1101_{(2)}=11\) 也就是在12这堆中,拿走11个硬币就达到了重新的平衡状态。

Tips:
(1)如果所有数成对出现,那么异或的最终结果是0.
(2)如果异或的最终结果是0,那它们不一定是成对出现的,比如 1,2,3,它们应成二进制就是01,10,11,异或在一起就是0,但不成对。

六、扩展问题

如果Nim游戏中的规则稍微变动一下,每次最多只能取K个,怎么处理?

方法是将每堆石子数mod (k+1).

七、C++ 代码

#include <iostream>

using namespace std;
const int N = 100010;

int main() {
    int n;
    scanf("%d", &n);

    int res = 0;
    while (n--) {
        int x;
        scanf("%d", &x);
        res ^= x;
    }

    if (res) puts("Yes");
    else puts("No");

    return 0;
}

标签:891,游戏,硬币,Nim,取子,异或,...
来源: https://www.cnblogs.com/littlehb/p/15391869.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有