P3185 [HNOI2007]分裂游戏 题解

· · 题解

题目链接:P3185 [HNOI2007]分裂游戏

把每一颗豆子看作一个子游戏。显而易见, 另外几颗豆子的状态不影响该豆子的结果。 因此我们可以对每一颗豆子分别当成一个游戏求 \text{sg} 函数。

对于每一个瓶子的豆子而言,有n颗豆子等价于有\text{n+2} 颗豆子。 因为若此时对手从 \text{a} 瓶子取 \text{1} 颗到 \text{b} 瓶子和 \text{c} 瓶子, 对手只需模仿操作一次即可复原状态。

因此凡一个瓶子有偶数颗豆子,等价于有 \text{0} 颗豆子, 我们可以直接跳过不考虑。有奇数颗豆子, 我们就直接认为该瓶子只有 \text{1} 颗豆子。

此时我们就可以自然的定义 \text{sg} 数组了。 设 \text{sg[i]} 为考虑第 \text{n-i+1} 个瓶子有 \text{1} 颗豆子时的 \text{sg} 函数。 (为了使 \text{n} 不同时的 \text{sg} 函数边界相同,必须倒着定义。)

对于任何个 \text{sg[i]} 的下一个状态

下面就是模板了。代码如下: ```cpp #include<cstdio> #include<cstring> using namespace std; int T,n,sg[25],a[25]; int init(int n){//预处理sg[n] bool vis[105]={}; if(sg[n]!=-1) return sg[n];//记忆化 if(n==1) return sg[1]=0;//边界 for(int i=1;i<n;i++){ for(int j=1;j<=i;j++) vis[init(i)^init(j)]=1;//下一个状态:sg[i][j] } for(int i=0;;i++) if(!vis[i]) return sg[n]=i; } int main(){ scanf("%d",&T); memset(sg,-1,sizeof(sg)); for(int i=1;i<=21;i++) if(sg[i]==-1) init(i);//预处理sg函数。 while(T--){ scanf("%d",&n); int ans=0,ans1=0,vi=0,vj=0,vk=0;//ans就是最终问题的sg值 ans1为满足情况的总数 //vi,vj,vk先都赋为0,-1即为最终答案,这样就可以不分类讨论。 for(int i=1;i<=n;i++){ scanf("%d",&a[i]); if(a[i]&1) ans^=sg[n-i+1];//只有i%2==1的情况是一个子问题。 } for(int i=1;i<=n-1;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])){ if(vi==0) vi=i,vj=j,vk=k; ans1++; } } } } printf("%d %d %d\n%d\n",vi-1,vj-1,vk-1,ans1); } return 0; } ```