[NOIP模拟][记忆化搜索][动态规划]游戏

题目描述:
题目大意:有n个物品排成一排,从左往右第i个价值为a[i],有两个人从左往右轮流取物品。第一个人可以拿一或两个物品。如果前一个人拿了k个,下一个人只能拿k或k+1个。如果剩下的物品不够拿,就结束。问如果大家都采取最优策略,那么先手拿的物品的价值最多能比后手多多少。(1≤n≤20000)
样例输入:

1
3
1 3 2

样例输出:

4

题目分析:
考场总结:考试时,觉得这道题特别像前面做得一道题分玩具,于是按照以前那个思路写dp,结果没写对,然后用同样的思路写了个暴力dfs,30分。结果考完评讲的时候,发现我的代码只须将每次dfs返回的值记录下来,记忆化一下就满分了!!!论30分与100分的差距~~~其实考试的时候主要是没有想到状态有重复。
分析:
搜索:其实每个人每次的选择面临的决策是一样的,都是想使自己拿的最大。当上一个人拿到了i且是通过拿到k个拿到的,当前拿的人就只有拿k个或拿k+1个两种选择。当前多的价值就是当前人拿的k个的价值和减去剩下部分能拿出的最大情况(另一个人会想拿的最大,即dfs的返回值)。于是以此dfs,然后记录下来,记忆化搜索一下。
DP:设dp[i][k]表示从第i个物品开始先手拿k个后最多能比后手多取的价值,则:
dp[i][k]=sum[i~i+k-1]+max(dp[i+k][k],dp[i+k+1][k+1])
注意到k最多取到200(20000<1+2+……+200),所以第二维只用开到200即可。
PS: 其实DP和dfs运用的思想是一样的。DP方法懒得写,于是DP的分析和代码都来自于cdsszjj的博客的博文NOIP模拟 Game 【博弈论】【动态规划】
附代码:
1、搜索:

#include<iostream>
#include<cstring>
#include<string>
#include<cstdlib>
#include<cstdio>
#include<ctime>
#include<cmath>
#include<cctype>
#include<iomanip>
#include<algorithm>
using namespace std;

const int N=20100;
int t,n,a[N],sum[N],dp[N][210];

int readint()
{
    char ch;int i=0,f=1;
    for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    if(ch=='-') {ch=getchar();f=-1;}
    for(;ch>='0'&&ch<='9';ch=getchar()) i=(i<<3)+(i<<1)+ch-'0';
    return i*f;
}

inline int dfs(int po,int k,int w)//po代表前一个人拿到了po,且是通过k拿的,w是那一段区间和
{
    if(po>n) return 0;
    if(dp[po][k]!=-1) return dp[po][k];//说明已经搜索过
    int ret,ret1,ret2;
    ret1=dfs(po+k,k,sum[po+k]-sum[po]);
    ret2=dfs(po+k+1,k+1,sum[po+k+1]-sum[po]);
    ret=max(ret1,ret2);
    return dp[po][k]=w-ret;
}

int main()
{
    //freopen("game.in","r",stdin);
    //freopen("game.out","w",stdout);

    t=readint();
    while(t--)
    {
        memset(dp,-1,sizeof(dp));
        n=readint();
        for(int i=1;i<=n;i++) a[i]=readint();
        for(int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i];
        printf("%d\n",max(dfs(1,1,sum[1]),dfs(2,2,sum[2])));
    }

    return 0;   
}

2、DP:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#define ll long long
using namespace std;

int getint()
{
    int i=0,f=1;char c;
    for(c=getchar();(c<'0'||c>'9')&&c!='-';c=getchar());
    if(c=='-')f=-1,c=getchar();
    for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
    return i*f;
}

const int N=20005,K=205;
int T,n;
ll a[N],dp[N][K];

int main()
{
    //freopen("game.in","r",stdin);
    //freopen("game.out","w",stdout);
    T=getint();
    while(T--)
    {
        memset(dp,0,sizeof(dp));
        memset(a,0,sizeof(a));
        n=getint();
        for(int i=1;i<=n;i++)
            a[i]=a[i-1]+getint();
        for(int i=n;i;i--)
            for(int k=1;i+k-1<=n&&k<=i+1&&k<=200;k++)
                dp[i][k]=a[i+k-1]-a[i-1]-max(dp[i+k][k],dp[i+k][k+1]);
        /*for(int i=1;i<=n;i++)
            for(int k=1;k<=n;k++)
                printf("dp[%d][%d]: %d\n",i,k,dp[i][k]);*/
        cout<<max(dp[1][1],dp[1][2])<<'\n';
    }
    return 0;
}
发布了99 篇原创文章 · 获赞 8 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qianguch/article/details/78396511