一道博弈题

考场上没想出来,最后讨论时发现精妙之处的一道题


精妙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]);
	}
} 
上一篇:2021-7-31 T-primes


下一篇:数学相关知识