题意:多组测试数据 ,输入 k个集合S的元素,m种情况,m种(L堆,每堆hi个)。
若存在移动某堆能到达一个必败点,则该点为必胜点,输出W
必败点指无论怎么移动都只能到达必胜点,输出L。
思路:SG函数
每堆看做一个子游戏,SG函数通过递归得到每种堆数的g();
SG函数
定义:
对于一个递增有界的图G(X, F)来说,SG函数g,是定义在X上的函数,函数值是非负整数,使得
用语言来描述就是:g(x)的值等于所有x的后继的SG函数中没有出现的最小非负整数。
对于递增有界的图,SG函数是唯一的、有界的。
所有的终止状态x,因为F(x)是空集,所以g(x)=0。
给出下图的SG函数。
例1
给出游戏1的SG函数,看看有什么规律,与P状态和N状态有什么关系。
x 0 1 2 3 4 5 6 7 8 9 10 11 …
g(x) 0 1 2 3 0 1 2 3 0 1 2 3 …
例2
有一堆石子,设当前剩下n颗石子,这一步至少要取走n/2取上界颗。唯一的终止状态是剩0颗石子。给出SG函数,看看有什么规律。
x 0 1 2 3 4 5 6 7 8 9 10 11 12 …
g(x) 0 1 2 2 3 3 3 3 4 4 4 4 4 …
根据例1的结果,我们猜测SG函数与P状态和N状态是有关的。如果g(x)=0,那么x就是P状态,否则x就是N状态。证明是很显然的,我们只要根据两者的定义,考虑以下三点:
l 如果x是终止状态,那么g(x)=0。
l 一个状态x,如果g(x)≠0,那么一定存在一个x的后继y,使得g(y)=0。
l 一个状态x,如果g(x)=0,那么所有x的后继y,都有g(y)≠0。
当然,SG函数还包含了其他的信息,这些信息在以后会用到。
代码:
#include<iostream> #include<string.h> #include<algorithm> using namespace std; int s[101]; int tt[10001]; int g(int x , int k) { int mex[101]; memset(mex,0,sizeof(mex)); if(tt[x]!=-1) return tt[x]; //集合S一致,则每个数量的mex一样,所以可以重复利用 if(x-s[0]<0) return tt[x]=0; //s[0]为集合中最小值,x-s[0]<0,则x不可能到达其他状态,一定为P for(int i=0;i<k && x-s[i]>=0;i++) //判断条件,因为s[]排了序,当x-s[i]<0就停止循环。 { mex[g(x-s[i] , k)]=1; //x的后继SG函数中的没有出现的最小非负数 } for(int i=0;;i++) //通过x的后继SG函数出现的非负数得x的结果 if(!mex[i]) return tt[x]=i; } int main() { int k ; int n, t ,a , ans; while(scanf("%d",&k)!=EOF && k) { memset(tt,-1,sizeof(tt)); tt[0]=0; for(int i=0;i<k;i++) scanf("%d",&s[i]); sort(s,s+k); scanf("%d",&t); while(t--) { ans=0; scanf("%d",&n); for(int i=0;i<n;i++) { scanf("%d",&a); ans^=g(a , k); } // for(int i=0;i<=12; i++) cout<<i<<" ";cout<<endl; //for(int i=0;i<=12;i++) cout<<tt[i]<<" ";cout<<endl; if(!ans) printf("L"); //异或sum!=0,说明该点为必败点,只能到达必胜点。 else printf("W"); } printf("\n"); } return 0; } /* (0,1,1) 的异或sum=0,为必败点. P必败点是指刚取过石头的人获胜,必败点无论怎么移动都只能到达必胜点。 */