版权声明:本文为博主原创文章,未经博主允许必须转载。 https://blog.csdn.net/C20181220_xiang_m_y/article/details/88624116
题目描述:
有n<=14根巧克力棒,每根有一个长度L<=109,开始时都在盒子里,每次操作可以把若干根巧克力棒从盒子里取出来,或者把一根已经取出来的巧克力棒吃掉正整数长度,最后不能操作者输,给定开始每根巧克力棒的长度,问先手是否必胜。
题目分析:
法一:非常暴力且通用的 算法
直接枚举后继算出SG值
设
表示还有s状态的巧克力棒未取出的SG值,
表示s状态的巧克力棒的nim和,那么
法二:分析平衡状态, 判断
假设有m根巧克力棒,它们长度的异或和为0(也就是nim游戏的必败态)
且剩下的n-m根巧克力棒的任意组合异或和都不为0。
那么先手只需要把这m根巧克力棒取出盒子,把nim游戏的必败态留给对方,如果对方又从盒子里取出一些巧克力棒,那么这些巧克力棒的nim和肯定不为0,先手只需要再把这些巧克力棒取成必败态即可,这样就可以做到始终把必败态留给对方。
也就是说,如果存在nim和为0的极大巧克力棒组合,那么先手必胜;
如果任何巧克力棒的组合nim和都不为0,那么先手必败。
看是否有异或和为0的组合即可。
法三:找到结论之后, 高斯消元判断
根据法二的分析,我们并不需要枚举所有巧克力棒选或不选。
把第
根巧克力棒的状态看成一个变量
,把长度
拆成二进制
,那么就是这样的一个方程组:
上面这个方程组的解的数量就是异或和为0的巧克力棒组合。
因为
要么为1要么为0,所以方程组的解的数量就是
。
由于每个x都为0显然是一组解,所以至少有一个自由元代表存在一个不为空的巧克力棒组合异或和为0,只要看是否有自由元即可。
写代码的时候k直接取到30就好了,时间复杂度
这里只贴出高斯消元的代码~:
#include<cstdio>
#include<algorithm>
using namespace std;
int n;
bool a[35][15];
bool Gauss(const int N){
for(int i=1;i<=n;i++){//n个变量
if(!a[i][i]) for(int j=i+1;j<=N;j++) if(a[j][i]) swap(a[i],a[j]);
if(!a[i][i]) return 1;//存在自由元
for(int j=i+1;j<=N;j++)
if(a[j][i]) for(int k=i;k<=n;k++) a[j][k]^=a[i][k];
}
return 0;
}
int main()
{
for(int t=1;t<=10;t++){
scanf("%d",&n);
for(int i=1,x,j;i<=n;i++)
for(scanf("%d",&x),j=0;j<30;j++) a[j+1][i]=x>>j&1;
puts(Gauss(30)?"NO":"YES");
}
}