游戏A:
甲乙两人面对若干堆石子,其中每一堆石子的数目可以任意确定。例如图1所示的初始局面:共n=3堆,其中第一堆的石子数a1=3,第二堆石子数a2=3,第三堆石子数a3=1。两人轮流按下列规则取走一些石子,游戏的规则如下:
每一步应取走至少一枚石子;
每一步只能从某一堆中取走部分或全部石子;
如果谁无法按规则取子,谁就是输家。
游戏B:
甲乙双方事先约定一个数m,并且每次取石子的数目不能超过m个;
其余规则同游戏A。
游戏C:
甲乙两人面对若干排石子,其中每一排石子的数目可以任意确定。例如图2
所示的初始局面:共n=3排,其中第一排的石子数
a1=7,第二排石子数a2=3,第三排石子数a3=3。两人轮流按下列规则取走一些石子,游戏的规则如下:
每一步必须从某一排中取走两枚石子;
这两枚石子必须是紧紧挨着的;
如果谁无法按规则取子,谁就是输家。
回忆游戏A的结论,以及它在游戏B上的推广,对于游戏C,我们的想法是
设计一个函数f,把函数f的值看作是二进制数。对于任意一个初始局面S,设S=(a1, a2, …, an),令#S=f(a1)+f(a2)+…+f(an)。若#S≠0,则先行者(甲)有必胜策略;否则#S=0,这时后行者(乙)有必胜策略。
游戏A中,f(x) = x。
游戏B中,f(x) = x mod (m + 1)。
游戏C中,f(x) = ?。
关键就在于如何构造一个满足要求的函数f。
回忆关于游戏A、B的结论的证明过程
函数f是否满足要求,关键在于#S是否满足下面的条件。
若#S=0,则无论先行者如何取子S→T,都有#T≠0。
若#S≠0,则先行者必然存在一种取子方法S→T,且#T=0。
用符号$(x),表示局面(x)的下一步所有可能出现的局面的集合。
在游戏A中,$(3)={(2), (1), (0)}。
在游戏B中,若m=4,则$(9)={(8), (7), (6), (5)},$(2)={(1), (0)}。
在游戏C中,$(7)={(5), (1, 4), (2, 3)}。
定义集合g(x):设$(x)={S1, S2, …, Sk},则g(x)={#S1, #S2, …, #Sk}。
在游戏A中,$(3)={(2), (1), (0)},故g(3)={#(2), #(1), #(0)}={10, 01, 00}。
在游戏B中,若m=4,则g(9)={#(8), #(7), #(6), #(5)},g(2)={#(1), #(0)}。
在游戏C中,g(7)={#(5), #(1, 4), #(2, 3)}。
用大写字母N表示非负整数集,即N={0, 1, 2, …}。
令N为全集,集合G(x)表示集合g(x)的补集。
定义函数f(n):f(n)=min{G(n)},即f(n)等于集合G(n)中的最小数。
设局面S=(a1, a2, …, an),#S=f(a1)+f(a2)+…+f(an),采用二进制数的加法。
若#S=0,则S负;若#S≠0,则S胜。
游戏C的f值:
g(0)={},G(0)={0, 1, …},f(0)=0;
g(1)={},G(1)={0, 1, …},f(1)=0;
g(2)={#(0)}={f(0)}={0},G(2)={1, 2, …},f(2)=1;
g(3)={#(1)}={f(1)}={0},G(2)={1, 2, …},f(3)=1;
g(4)={#(2), #(1, 1)}={f(2), f(1)+f(1)}={1, 0},G(4)={2, 3, …},f(4)=2;
g(5)={#(3), #(1, 2)}={f(3), f(1)+f(2)}={1, 1},G(5)={0, 2, 3, …},f(5)=0;
g(6)={#(4), #(1, 4), #(2, 2)}={2, 1, 0},G(6)={3, 4, …},f(6)=3;
g(7)={#(4), #(1, 4), #(2, 3)}={2, 2, 0},G(7)={1, 3, 4, …},f(7)=1;
结论
此类搏弈游戏的一般性解法:
用一个n元组(a1, a2, …, an),来描述游戏过程中的一个局面。
用符号#S,表示局面S所对应的二进制数。
用符号$(x),表示局面(x)的下一步所有可能出现的局面的集合。
定义集合g(x):设$(x)={S1, S2, …, Sk},则g(x)={#S1, #S2, …, #Sk}。
令非负整数集为全集,集合G(x)表示集合g(x)的补集。
定义函数f(n):f(n)=min{G(n)},即f(n)等于集合G(n)中的最小数。
设局面S=(a1, a2, …, an),#S=f(a1)+f(a2)+…+f(an),采用二进制数的加法。
若#S≠0,则先行者有必胜策略;若#S=0,则后行者有必胜策略。适用范围和限制条件:
甲乙两人取石子游戏及其类似的游戏;
每一步只能对某一堆石子进行操作;
每一步操作的限制,只与这堆石子的数目或一些常数有关;
操作在有限步内终止,并不会出现循环;
谁无法继续操作,谁就是输家。
游戏D(POI’2000,Stripes):
一排石子有L个,甲乙两人轮流从中取“紧紧挨着的”A或B或C枚石子。谁不能取了,谁就是输家。已知A, B, C, L,问甲乙二人谁有必胜策略。
#include<cstdlib>
#include<stdio.h>
#include<string.h>
using namespace std;
int color[3];//连续取的石子的颗数
int fx[1010];//函数f(x),f(x)不为0,为利己态。
/*
**用符号$(x),表示局面(x)的下一步所有可能出现的局面的集合。
**定义集合g(x):设$(x)={S1, S2, …, Sk},则g(x)={#S1, #S2, …, #Sk}。
**用大写字母N表示非负整数集,即N={0, 1, 2, …}。
**令N为全集,集合G(x)表示集合g(x)的补集。
**定义函数f(n):f(n)=min{G(n)},即f(n)等于集合G(n)中的最小数。
*/
void init()
{
for(int i=0;i<=1000;i++)//枚举每段区间[0,i]
{
int g[1010];//记录局面x下一步出现的所有的局面的结合:g(x)
memset(g,0,sizeof(g));
for(int j=0;j<3;j++)//石子的三种数量
for(int k=color[j];k<=i;k++)
{
//在区间[0,i]中,在不同位置取走color[j]颗石子
//即k是这color[j]颗石子的右端点。
int t=fx[k-color[j]]^fx[i-k];
//k-color[j]是左边剩下的石子数量,成为一个局新面;
//i-k为右边剩下的石子的数量,成为一个新局面
g[t]=1;
//标记g(#(left局面,right局面)),
//其中,#(left局面,right局面)=left局面^right局面.
}
for(int j=0;j<=1000;j++)
if(!g[j])
{ //G(x)为g(x)的补集,放f(x)=min(G(x)),
//而集合g(x)的所有元素已经标记为1;
//所以对于第一个等于0的g[j],j就是min(G(x))
fx[i]=j;
//f(i)的解为min(G(x)),也就是j
break;
}
}
}
int main()
{
while(scanf("%d",&color[0])!=EOF)
{
for(int i=1;i<3;i++)
scanf("%d",&color[i]);
memset(fx,0,sizeof(fx));
init();
int t;
scanf("%d",&t);
while(t--)
{
int n;
scanf("%d",&n);
if(fx[n]==0) puts("2");
else puts("1");
}
}
}