考场上没想出来,最后讨论时发现精妙之处的一道题
精妙1:
强强和笨笨都足够聪明和自私,那么他们如果能赢,一定走最小步数。
而如果他们输了,一定让另一个人走最多步数
这是解题的其中一个关键
精妙2:
因为两人必定决出胜负,所以每个问题不是输,就是赢,
看似是一句废话,其实为最后的递推作了铺垫。
有了这两个精妙之处,就可以解题了
第一步:筛素数,随便你怎么筛
第二步:开始递推
g(i)表示从i到0的回合数(输了)
f(i)表示从i到0的回合数(答案)
为什么要这么记呢?,看下面的递推
若f(i-prime(k))==-1(表示i-prime(k))输了,此时f(i)=min(f(i),g(i-prime(k))+1);
若所有f(i-prime(k))都不等于-1,则g(i)=max(g(i),f(i-prime(k)+1)并且f(i-prime(k)>0
f的递推全由g转来,而g的递推全由f转来。
怎样想明白为什么g要取最大值而f要取最小值?
自己的理解:如果把自己置身于笨笨,则会看不清楚。若把自己置身于游戏中,就会恍然大悟。
g(i)不仅仅时笨笨一人的状态,同时也是强强的状态,若笨笨在f(i+prime)则强强要走g(i)此时强强必输,而强强很聪明,所以就很好理解为什么要取max了。
最后上代码:
#include<bits/stdc++.h>
using namespace std;
int tot,f[20005],p[20005],g[20005],a[20005],n,x;
int main()
{
for(int i=2;i<=20000;i++)
{
if(a[i]==0)tot++,p[tot]=i;
for(int j=2;j<=20000/i;j++)
{
a[i*j]=1;
}
}
a[1]=1;
g[0]=0;
g[1]=0;
memset(f,-1,sizeof(f));
for(int i=1;i<=20000;i++)
{
for(int j=tot;j>=1;j--)
{
if(p[j]>i)continue;
if(f[i-p[j]]==-1)
{
if(f[i]==-1)
{
f[i]=1e9;
}
f[i]=min(f[i],g[i-p[j]]+1);
}
}
if(f[i]==-1)
{
for(int j=tot;j>=1;j--)
{
if(p[j]>i)continue;
if(f[i-p[j]]>0)
g[i]=max(g[i],f[i-p[j]]+1);
}
}
}
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&x);
printf("%d\n",f[x]);
}
}