You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

5.0 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

##AcWing 891. Nim游戏

一、题目描述

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

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

输入格式 第一行包含整数 n

第二行包含 n 个数字,其中第 i 个数字表示第 i 堆石子的数量。

输出格式 如果先手方必胜,则输出 Yes

否则,输出 No

数据范围 1≤n≤10^5,1≤每堆石子数≤10^9

输入样例

2
2 3

输出样例

Yes

二、历史与传说

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

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

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

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

三、博弈论结论

在两名选手都足够聪明,每一步都是最优解的情况下:

a_1 ^ a_2 ^ ... ^a_n=0 先手必败 a_1 ^ a_2 ^ ... ^a_n\neq0 先手必胜 就是把所有堆的石子个数异或起来,结果是零,先手必败,不是零,先手必胜。

四、证明过程

1、结论1

操作到最后时,每堆石子数都是0,有0 ^ 0 ^ …0=0

2、结论2

在操作过程中,如果 a_1 ∧ a_2 ∧ … ∧ a_n=x≠0。那么玩家必然可以通过拿走某一堆若干个石子将异或结果变为0

证明

  • 不妨设结果x的二进制表示中最高一位1在第k
  • 那么在a_1,a_2,…,a_n中,必然有一个数a_i,它的第k位是1(要不那个1从哪里来的?),且a_i ∧ x<a_i:
  • 让第i堆石子保留a_i ∧ x个:即从第i堆石子中拿走a_i(a_i ∧ x)个石子
  • 此时新状态的异或和:
    \large a_1 ∧ a_2 ∧ … ∧ a_{i^{'}}  ∧ … a_n \\ 
      =a_1 ∧ a_2 ∧ … ∧(a_i ∧ x) ∧ a_n  \\ 
      =a_1 ∧ a_2 ∧ … ∧a_i  ∧... ∧ a_n ∧ x \\  
      =x ∧ x  =0
    

证毕

解释:也就是说,不管给我啥样的情况,我都能找出一种办法,使得一次拿取后满足异或和为0

其实,我们也并不非得要找到某个有明显特征(指x的最高位1在第k位,而a_i的第k位是1)的a_i,更通用的作法是

for (int i = 0; i < n; i++) {
    if((a[i] ^ x) < a[i]){
      1. 拿走 a[i]-(a[i] ^ x)
      2. 剩下 a[i] ^ x
    }
}

这样的话,我们也就可以求出有多少种拿法,并且知道每种拿法是拿走多少,剩下多少。

3、结论3

在操作过程中,如果 a_1 ∧ a_2 ∧ …∧ a_n=0,那么无论玩家怎么拿,必然会导致最终异或结果不为0

反证法:假设玩家从第i堆石子拿走若干个,结果仍是0。不妨设还剩下a_i^{}个,因为不能1个都不拿,所以0≤a_i^{}<a_i,且a_1 ∧ a_2∧…∧a_i^{}∧…∧a_n=0

进行构造

(a_1 ∧ a_2∧…∧a_i∧…a_n)∧(a_1∧a_2∧…∧a_i^{}∧…∧a_n)

整体构造完式子后,利用异或运算的 结合律 性质,让

a_1 ∧ a_1=0,a_2 ∧ a_2=0,a_3 ∧ a_3=0,...
\large \Rightarrow a_i∧a^{}=0

a_i=a,与假设0≤a<a_i矛盾。

证毕

基于上述三个结论

  1. 如果先手面对的局面是x=a_1∧a_2∧…∧a_n≠0,那么先手总可以通过拿走某一堆若干个石子(a_i ∧ x个),将局面变成a_1∧a_2∧…∧a_n=0。如此重复,最后一定是后手面临最终没有石子可拿的状态。先手必胜。
  2. 如果先手面对的局面是a_1∧a_2∧…∧a_n=0,那么无论先手怎么拿,都会将局面变成a_1∧a_2∧…∧a_n≠0,那么后手总可以通过拿走某一堆若干个石子,将局面变成a_1∧a_2∧…∧a_n=0。如此重复,最后一定是先手面临最终没有石子可拿的状态,先手必败。

五、实现代码

#include <bits/stdc++.h>

using namespace std;

int main() {
    int n;
    cin >> n;

    int res = 0; // 起始值是0,因为任何数与0进行异或都它本身
    while (n--) {
        int x;
        cin >> x;
        res ^= x;
    }
    if (res)
        puts("Yes"); // 异或值非零,必胜
    else
        puts("No"); // 异或值是零,必败
    return 0;
}