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; } }