P1857 质数取石子 (DP,递推)

题目描述

桌上有若干个石子,每次可以取质数个。谁先取不了,谁就输。问最少几步能赢?(一个人取一次算一步)

输入输出格式

输入格式:

第一行N,表示有N组数据

接下来N行为石子数

输出格式:

每组数据一个数,若必胜,则输出最少步数,否则输出-1

输入输出样例

输入样例#1: 
3
8
9
16
输出样例#1: 
1
-1
3

说明

石子数≤20000,N≤10

Solution

NIm 游戏的质数版 思路基本和原版一致 即每一个必胜状态都由上一次必败的状态转过来.然后需要先一个筛法筛出20000以内的质数. 然后照Nim 游戏的动规方程 即可.不过这里的状态要多加一维用于加上场数这个信息的保存. 这里用的是

f[i][0] 代表场数

f[i][1] 代表 

  1 -->  必胜

  -1 --> 必败

细节就是质数枚举的时候要倒着从大的开始 否则会有鬼.

代码 :

#include<bits/stdc++.h>
using namespace std;
int n,f[20010][2]={0},a[20];
int p[3000],maxn=-1,sum;

int v[20008];
void pre()
{
    for(int i=2;i<=20008;i++)
    {
        if(v[i]==0)
        {
        p[++sum]=i;
        for(int j=1;j*i<=20001;j++)
            v[i*j]++;
        }
    }
}

 void solve()
{
    f[0][1]=-1;f[0][0]=0;
    f[1][1]=-1;f[1][0]=0;
    f[2][1]=1;f[2][0]=1;
    f[3][1]=1;f[3][0]=1;//初始化几个点
    for(int i=4;i<=maxn;++i)
    for(int j=sum;j>=1;--j)
    if(p[j]<=i)
    { 
        if(f[i-p[j]][1]==-1)            //前驱为必败
        if(f[i][1]==0||f[i][1]==-1)//尚未判定状态
        {
            f[i][1]=1;                        
            f[i][0]=f[i-p[j]][0]+1;
        }
        else
        f[i][0]=min(f[i][0],f[i-p[j]][0]+1);//选取场数最小值
        if(f[i-p[j]][1]==1)//前驱为必胜
        if(f[i][1]==0)
        {
            f[i][1]=-1;
            f[i][0]=f[i-p[j]][0]+1;
        }
        else if(f[i][1]==-1)
        f[i][0]=max(f[i][0],f[i-p[j]][0]+1); //败了久选最大值.
    }
    return;
}

int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        maxn=max(a[i],maxn);
    }
   pre();
    solve();
    for(int i=1;i<=n;i++)
    if(f[a[i]][1]==-1)
    printf("-1
");
    else printf("%d
",f[a[i]][0]);
    return 0;
}
原文地址:https://www.cnblogs.com/Kv-Stalin/p/8646637.html