分裂游戏 bzoj-1188 HNOI-2007
题目大意:题目链接。
注释:略。
想法:
我们发现如果一个瓶子内的小球个数是奇数才是有效的。
所以我们就可以将问题变成了一个瓶子里最多只有一个球球。
设$sg(x)$表示位置为$x$的小球的$sg$值。
显然通过$n^2$暴力转移即可。
求出了所有点的$sg$值之后,把所有有奇数个小球的位置用$SG$定理异或起来即可啦。
Code:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 25
using namespace std;
inline char nc() {static char *p1,*p2,buf[100000]; return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;}
int rd() {int x=0; char c=nc(); while(!isdigit(c)) c=nc(); while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=nc(); return x;}
int a[26],sg[26];
void pre()
{
bool mark[20001];
sg[1]=0;
for(int i=2;i<=25;i++)
{
memset(mark,0,sizeof(mark));
for(int j=1;j<i;j++)
for(int k=1;k<=j;k++)
mark[sg[j]^sg[k]]=1;
for(int j=0;;j++)
if(!mark[j]){sg[i]=j;break;}
}
// for(int i=0;i<=22;i++) printf("%d ",sg[i]); puts("");
}
int main()
{
int t,n;
pre();
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
int ans=0,tot=0;
for(int i=1;i<=n;i++)
if(a[i]&1)ans^=sg[n-i+1];
for(int i=1;i<=n;i++) if(a[i])
for(int j=i+1;j<=n;j++)
for(int k=j;k<=n;k++)
if((ans^sg[n-i+1]^sg[n-j+1]^sg[n-k+1])==0)
{
tot++;
if(tot==1)printf("%d %d %d\n",i-1,j-1,k-1);
}
if(!tot)printf("-1 -1 -1\n");
printf("%d\n",tot);
}
return 0;
}
小结:博弈论真的考验思维。注意$sg$的更新时定义的是位置而不是距离,所以在统计答案的时候需要用$n-i+1$。