学了新的忘了旧的,还活着干什么

题意:一些盒子,每步可选择打开盒子和取出已打开盒子的任意多石子,问先手是否必胜

搬运po姐的题解:

先手必胜的状态为:给出的数字集合存在一个异或和为零的非空子集,则先手必胜

证明:

首先我们有状态A:当前的所有打开的箱子中的石子数异或和为零,且所有关闭的箱子中的石子数的集合中不存在一个异或和为零的非空子集

易证A状态时先手必败

先手有两种操作:

1.从一个打开的箱子中拿走一些石子 那么根据Nim的结论 后手可以同样拿走一些石子使状态恢复为A状态

2.打开一些箱子 由于未打开的箱子中不存在一个异或和为零的非空子集 所以打开后所有打开的箱子中石子数异或和必不为零 于是后手可以拿走一些石子使状态恢复为A状态

故此时先手必败

那么如果初始不存在一个异或和为零的非空子集,那么初始状态满足状态A,先手必败

如果初始存在一个异或和为零的非空子集,那么先手一定可以打开所有的异或和为零的子集,使剩余箱子不存在异或和为零的非空子集,将状态A留给后手,先手必胜

 然后就是判断是否有子集异或为0,线性基求一下。

update:其实当n>32时可以直接判断先手胜,因为int范围考虑每一个二进制位一定会有异或为0的 

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define N 35
 4 int read(){
 5   int x=0,f=1;char ch=getchar();
 6   while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
 7   while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
 8   return x*f;
 9 }
10 int n,a[N],b[N];
11 bool gauss(){
12   memset(b,0,sizeof(b));
13   for(int i=1;i<=n;i++){
14     for(int j=30;j>=0;j--)
15       if(a[i]>>j&1){
16         if(!b[j]){b[j]=a[i];break;}
17         else a[i]^=b[j];
18       }
19     if(!a[i])return 1;
20   }
21   return 0;
22 }
23 int main(){
24   int T=read();
25   while(T--){
26     n=read();
27     for(int i=1;i<=n;i++)a[i]=read();
28     puts(gauss()?"Yes":"No");
29   }
30   return 0;
31 }
View Code

相关文章: