[HNOI2007]分裂游戏
题目大意:
有n个格子,每个格子有一些石子,其中第i个格子中有p[i]个石子。
有两个人A和B轮流进行操作,每次可以选定三个格子i<j<=k,从i中取出一个石子,并在j和k中各放入一个石子。
不能进行操作的人输。
问先手是否有必胜策略,如果有,请输出第一次操做的取法。
如果第一次可以有多种操作,求出字典序最小的,并求出取法的个数。
思路:
如果用不同瓶子中石子的个数作为游戏的状态,显然存不下。
这里我们用一个石子所在的格子编号表示游戏状态。
若当前有一个石子在i格子,那么它的后继状态就是p[i]-1,p[j]+1,p[k]+1。
则sg(i)=mex{sg(j)^sg(k)|i<j<=k}。
由于n的范围比较小,最后枚举所有的i,j,k,暴力求出答案即可。
1 #include<cstdio> 2 #include<cctype> 3 #include<vector> 4 #include<cstring> 5 #include<algorithm> 6 inline int getint() { 7 register char ch; 8 register bool neg=false; 9 while(!isdigit(ch=getchar())) if(ch=='-') neg=true; 10 register int x=ch^'0'; 11 while(isdigit(ch=getchar())) x=(((x<<2)+x)<<1)+(ch^'0'); 12 return neg?-x:x; 13 } 14 const int N=22; 15 int p[N],sg[N]; 16 class Set { 17 private: 18 std::vector<int> v; 19 public: 20 Set() { 21 v.clear(); 22 v.push_back(-1); 23 } 24 void insert(const int x) { 25 v.push_back(x); 26 } 27 int mex() { 28 std::sort(v.begin(),v.end()); 29 v.resize(std::unique(v.begin(),v.end())-v.begin()); 30 for(register std::vector<int>::iterator i=v.begin();i!=v.end()-1;i++) { 31 if(*(i+1)!=*i+1) return *i+1; 32 } 33 return *(v.end()-1)+1; 34 } 35 }; 36 int n; 37 int getsg(const int &i) { 38 if(~sg[i]) return sg[i]; 39 Set s; 40 for(int j=i+1;j<=n;j++) { 41 for(int k=j;k<=n;k++) { 42 s.insert(getsg(j)^getsg(k)); 43 } 44 } 45 return sg[i]=s.mex(); 46 } 47 int main() { 48 for(register int T=getint();T;T--) { 49 n=getint(); 50 for(register int i=1;i<=n;i++) { 51 p[i]=getint(); 52 } 53 memset(sg,-1,sizeof sg); 54 sg[n]=0; 55 int ans=0; 56 for(register int i=1;i<n;i++) { 57 if(!p[i]) continue; 58 for(register int j=i+1;j<=n;j++) { 59 for(register int k=j;k<=n;k++) { 60 p[i]--,p[j]++,p[k]++; 61 int tmp=0; 62 for(register int l=1;l<=n;l++) { 63 if(p[l]&1) { 64 tmp^=getsg(l); 65 } 66 } 67 if(!tmp) { 68 if(!ans) { 69 printf("%d %d %d\n",i-1,j-1,k-1); 70 } 71 ans++; 72 } 73 p[i]++,p[j]--,p[k]--; 74 } 75 } 76 } 77 if(!ans) puts("-1 -1 -1"); 78 printf("%d\n",ans); 79 } 80 return 0; 81 }