P2197 nim游戏

传送门

经典的博弈

如果只有两堆

那么结束状态就是(0,0)

考虑先手

怎样保持后手面对(0,0)

如果两堆大小不一样

那么先手只要保持两堆一样大就行了

先手先取大的一堆

使两堆一样大

后手无论取多少

先手只要在另一堆取一样多

最后就一定是后手面对(0,0)

但是如果两堆一样多...

那先手取完

就变成两堆不一样多,后手先取了

那么先手必输

 

那么对于很多堆的情况

先手也要尽量使后手面对结束状态

有一个结论:

如果每堆的数量的异或和(xor,C语言中是" ^ ")不为 0

那么先手必胜,反之先手必输

证明:


 

 

1. 对于总异或和 k ,如果不为 0

对于 k 在2进制下的最高位 i 的 1

肯定至少有一个原数 N 在2进制下的第 i 位为 1 (不然就不可能使 k 在第 i 位为1,原因请见 异或的定义

所以如果我们要让 k 异或一个数 x ,使 k 变为 0,那么显然当x = k时是可以的

所以如果我们把 N 成 N^k, 那么总异或和就变成 0 了(显然总异或和会变成 k^k = 0)

显然N^k 一定是小于 N 的

 

因为 N 和 k 的最高位 i 都为 1

异或一下第 i 位就变成 0 了

相当于变小了

所以 N^k 一定小于 N

所以只要把 N 减某个数就一定能变成 N^k;

 

2. 如果 总异或和为 k 为 0

那么如果你把任何一个堆的数减小任何一个数都会导致 k 变成不为 0 的数

证明也不难

把一个数减小,那么它至少其中一位会改变

如果有一位改变了,那么异或和肯定也改变了(原因请见 异或的定义,稍微想一想就懂了)

那异或和就不是 0 了;

 

好了有了以上两点就容易了

对于先手,如果异或和 k 不为 0

那就把 k 变成 0

那么后手就只能把 k 变成一个不为 0 的数

那先手就再把 k 变为 0

一直减下去

当所有数都减完了

k=0,肯定是后手面对这种状态

所以先手必胜

反之

如果一开始异或和 k 为 0...

那么先手只能让 k 变成不为 0 的数

然后后手笑了...

先手就必输了


 

证明完毕

有了结论就很简单了

 

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int t,n,a,k;
int main()
{
    cin>>t;
    while(t--)
    {
        k=0;
        cin>>n;
        for(int i=1;i<=n;i++)
        {
            cin>>a;
            k^=a;
        }
        if(k) cout<<"Yes"<<endl;
        else cout<<"No"<<endl;
    }
}

 

posted @ 2018-08-30 14:56  LLTYYC  阅读(208)  评论(0编辑  收藏  举报