题目
贝茜和约翰在玩一个数字游戏.贝茜需要你帮助她。游戏一共进行了G(1≤G≤100)场。第i场游戏开始于一个正整数Ni(l≤Ni≤1,000,000)。游戏规则是这样的:双方轮流操作,将当前的数字减去一个数,这个数可以是当前数字的最大数码,也可以是最小的非0数码.比如当前的数是3014,操作者可以减去1变成3013,也可以减去4变成3010。若干次操作之后,这个数字会变成0.这时候不能再操作的一方为输家。
贝茜总是先开始操作.如果贝茜和约翰都足够聪明,执行最好的策略.请你计算最后的赢家。
题解1
博弈SG函数公式sg[i]=mex{ sg[j] }(要求i->j,j是i的后继情况)
易知0是先手为负,令sg[0]=0。
以后的sg[i]=mex{ sg[i-mx],sg[i-mn] }(其中mx,mn,分别是最大,最小数码)
预处理所有sg后O(1)回答问题。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1000010;
int sg[maxn];//sg值的范围0~2
bool v[10];
int mex()
{
for(int i=0;;i++)
{
if(!v[i]) return i;
}
}
void get_sg(int q)
{
sg[0]=0;
for(int i=1;i<=q;i++)
{
int j=i,mx=-1,mn=10;
while(j>0)
{
if(j%10!=0) mx=max(mx,j%10),mn=min(mn,j%10);
j/=10;
}
memset(v,false,sizeof(v));
if(mx!=-1) v[sg[i-mx]]=true;
if(mn!=10) v[sg[i-mn]]=true;
sg[i]=mex();
}
}
int main()
{
get_sg(1000000);
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
if(sg[x]==0) printf("NO\n");//sg=0,先手必败
else printf("YES\n");//sg>0,先手必胜
}
return 0;
}
题解2
博弈SG函数这回码的sg就不是真正的sg了,因为这题中sg只在乎0和大于0,干脆分别用0和1表示。
这次sg的求值不用公式了,根据其原理来求:
若当前状态的子状态中有必败状态,则该状态为必胜状态;
若当前状态的子状态中无必败状态(即都是必胜状态),则该状态为必败状态。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1000010;
int sg[maxn];//对于数字为i能赢为1,否则为0
int dfs(int q)
{
if(sg[q]!=-1) return sg[q];
int i=q,mx=-1,mn=10;
while(i>0)
{
if(i%10!=0) mx=max(mx,i%10),mn=min(mn,i%10);
i/=10;
}
sg[q]=0;//有1个为0则sg为1,全都不为0则sg为0
if(mx!=-1)
if(dfs(q-mx)==0) sg[q]=1;
if(mn!=9 )
if(dfs(q-mn)==0) sg[q]=1;
return sg[q];
}
int main()
{
memset(sg,-1,sizeof(sg));sg[0]=0;
dfs(1000000);
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
if(sg[x]==0) printf("NO\n");
else printf("YES\n");
}
return 0;
}