【BZOJ】1188 [HNOI2007]分裂游戏

【算法】博弈论

【题解】

我们的目的是把游戏拆分成互不影响的子游戏,考虑游戏内的转移

如果把每堆视为子游戏,游戏之间会相互影响,不成立。

将每堆的一个石子视为子游戏,其产生的石子都在同一个子游戏中。

虽然每堆的每个石子都是不同的子游戏,但显然SG值是可以共用的

SG[x]表示第x堆上一个石子的SG值,边界SG[n]=0。

考虑转移,对第i堆上一个石子操作可能会有多种向后放的方案,每一种方案的SG值是sg[j]^sg[k](因为这个局面包含两个子局面各自sg值,异或得到总局面sg值)

那么对于同一堆的多个石子都是一模一样的子游戏,偶数由于异或自反性可以抵消,奇数剩1。

最后考虑第一步操作,是要将异或值变为0,不讲它视为某个子游戏的第一步,只是单单看作增减子游戏。

拿掉一个i或增加一个j和k,都是多异或一个g[],所以找到字典序最小的ijk使ans^g[i]^g[j]^g[k]=0即可,注意a[i]>0。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int g[30],a[30],n,T;
bool h[60];
int main()
{
    g[0]=0;
    for(int i=1;i<=30;i++)
    {
        memset(h,0,sizeof(h));
        for(int j=i-1;j>=0;j--) 
        for(int k=j;k>=0;k--)
        h[g[j]^g[k]]=1;
        for(int j=0;j<=60;j++)if(!h[j]){g[i]=j;break;}
    }
    scanf("%d",&T);
    while(T--)
    {
         scanf("%d",&n);
         int ans=0,ansnum=0;//顺手开错变量类型 
         bool ok=0;
         for(int i=1;i<=n;i++)
         {
             scanf("%d",&a[i]);
             if(a[i]&1)ans^=g[n-i];
         }
         for(int i=1;i<=n;i++)
         for(int j=i+1;j<=n;j++)
         for(int k=j;k<=n;k++)
         if(a[i]>0&&(ans^g[n-i]^g[n-j]^g[n-k])==0)
         {
             if(!ok){printf("%d %d %d\n",i-1,j-1,k-1);ok=1;}
             ansnum++;
         }
         if(!ansnum)printf("-1 -1 -1\n");
         printf("%d\n",ansnum);
    }
    return 0;
}         
View Code

 

posted @ 2017-07-24 19:17  ONION_CYC  阅读(280)  评论(0编辑  收藏  举报