【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。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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; }