睾♂手过招
题意描述:给定n个长度为20的,由0和1组成的序列。对于每个序列,都可以进行操作:将任意一个1移动到其右边的第一个0处。
每次可以选择任意一个序列中的任意一个1操作。不能操作者输。给定初始n个序列,求是否为必胜态。
题解:
SG函数万岁
SG定理:对于任意一个状态 $ x $ ,如果其SG函数值 $ SG(x)=0 $ 则 $ x $ 为必败态,否则为必胜态。
定义其SG函数值 $ SG(x) $ 为不属于其所有后继状态的SG函数值集合的最小非负整数。
即: $ SG(x)=mex(S) $ 其中S是x的 后继状态的SG函数值集合。
比如,若x有5个后继状态,其SG函数值分别为1,5,2,0,4,则 $ SG(x)=3 $
对于某个游戏和(如本题中n个序列),其SG函数为其子游戏(本题中每个序列)的SG函数的Nim(异或)和。
比如,若对于某个游戏和,其子游戏的SG函数值分别为1,5,2,0,4,则该游戏和的SG函数值为1^5^2^0^4=2。
一些针对本题的技巧
由于只有20位,采用状压的方式保存、求每个状态的SG函数。
话说最近沉迷位运算无法自拔......
首先我们可以想到一个预处理:如果一个序列,右边全是一段连续的1,那么必不可能走下一步,其SG函数值为0。
于是便有:
for(int i(0);i<=20;i++){
SG[(1<
然后是求SG函数的:利用SG函数的定义,求出所有子状态的SG函数,然后:
如果最小的函数值大于0,则取0;
否则,取第一个「空隙」的位置。
如果自始至终都没有「空隙」,那么就取最大一个函数值+1。
这一段位运算有点难懂,直接贴代码
int sg(int x){
if(SG[x]!=-1)return SG[x];
int cnt=0;
int t=(x+1)-lb(x+1);//去掉末尾连续1后
int k=lb(t);
while(k){
t=t^k;//从t中抹掉k
int tmp=k;//找到从tmp往右数第一个空位
while((x^k)>1;//找到从tmp往右数第一个空位
}
//new:x^k^tmp
cnt++;
a[cnt]=sg(x^k^tmp);//记录所有x子状态的sg值。
k=lb(t);
}
sort(a+1,a+1+cnt);
if(a[1]>0)return SG[x]=0;
for(int i(1);i<=cnt;i++){
if(a[i]-a[i-1]>1)return SG[x]=a[i-1]+1;
}
return SG[x]=a[cnt]+1;
}