Cpp环境【USACO3.3.5】【CQYZOS1256】A Game 游戏

【问题描述】  

  有如下一个双人游戏:
  N个正整数的序列放在一个游戏平台上,游戏由玩家1开始,两人轮流从序列的两端取数,取数后该数字被去掉并累加到本玩家的得分中,当数取尽时,游戏结束。以最终得分多者为胜。
  编一个执行最优策略的程序,最优策略就是使自己能得到在当前情况下最大的可能的总分的策略。你的程序要始终为两位玩家执行最优策略。

【输入格式】  

第一行: 正整数N, 表示序列中正整数的个数。
第二行至末尾: 用空格分隔的N个正整数(大小为1-200)。

【输出格式】  

只有一行,用空格分隔的两个整数: 依次为玩家一和玩家二最终的得分。

【输入样例】  

6
4 7 2 9 5 2

【输出样例】  

18 11

【数据范围】  

2 <= N <= 100

【传送矩阵】

重庆一中题库 1256 传送矩阵

【思路梳理】

  思考一下经典的一道区间型动态规划问题:

取数游戏:
  1、每次取数时须从取走一个元素,共取n次;
  2、每次取走的元素只能是当前序列的首或尾元素;
  3、每次取数都有一个得分值,取数的得分=被取走的元素值*2^i,其中i表示第i次取数(从1开始编号);
  4、游戏结束总得分为n次取数得分之和。

  两道题目有异曲同工之处,先从相同点入手:都是常规的动态规划的思路,思考从哪个状态转移过来。显然对于当前状态,我们可以取走当前区间的第一个数或者最后一个数,那么我们的状态转移函数仍然设计为:d[i][j]=在i~j个区间上进行取数时能够获得的最大分数值。
  现在问题在于差异处:两个人进行游戏。一种容易想到的思路是:将d[i][j]拓展一维变成d[i][j][k]=在第i~j个数中取数时,上一位玩家共取得的得分为k的情况下,当前玩家的分数。然而这样一来首先是空间复杂度非常高,再者时间复杂度也不能够接受。
  如此一来我们必须要思考两个玩家间取得得分的关系:在区间i~j中无论两位玩家采取怎么样的策略取数,都一定会有如下等式:sum[i][j]=甲玩家在i~j区间取数的分数值+乙玩家在i~j区间取数的分数值。那么由此一来我们可以设置一个可行的状态函数:
  

d[i][j]=先手玩家在第i个元素至第j个元素中进行取数时能够获得的最大值

  为了建立状态转移方程,我们需要思考当前状态d[i][j]如何转移过来。当前先手玩家甲只有唯二的选择,要么选择第i个数字,要么选择第j个数字。
  如果选择了第一个选择,那么就是由“在第i+1个数至第j个数后手取数(即乙玩家先手取数)所能够获得的最大分数”转移过来。两人都遵循的是最优策略,那么乙玩家所取得的分数是d[i+1][j](乙是先手玩家,在第i+1至第j个数中采用最优策略获得最大分数值)。
  根据我们上面推出的等式,可以得到甲在乙取数后所获得的分数是sum[i+1][j]-d[i+1][j],同时甲玩家在新的一轮作为先手玩家取走了第i个数字,那么t1=sum[i+1][j]-d[i+1][j]+a[i],同理可以得到t2=sum[i][j-1]-d[i][j-1]+a[j],然后d[i][j]=max(t1,t2),取走最优的答案。

【Cpp代码】
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define maxn 105
#define inf 20005
using namespace std;
int n,a[maxn],d[maxn][maxn],sum[maxn][maxn];


void dp()
{
    //d[i][j]=在第i至第j个区间进行取数时,甲玩家能够获得的最大分数值
    for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)if(i!=j)   
        d[i][j]=-inf;
    else d[i][j]=a[i];

    //d[i][j]=max{ sum[i+1][j]-d[i+1][j],sum[i][j-1]-d[i][j-1]}

    for(int lens=2;lens<=n;lens++)
    for(int i=1;i<=n;i++)
    {
        int j=i+lens-1;
        if(j>n) break;
        int t1=sum[i+1][j]-d[i+1][j]+a[i],t2=sum[i][j-1]-d[i][j-1]+a[j];
        d[i][j]=max(t2,t1);
    }
    cout<<d[1][n]<<" "<<sum[1][n]-d[1][n];
}

int main()
{
//  freopen("in.txt","r",stdin);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)   scanf("%d",&a[i]);

    for(int i=1;i<=n;i++)
    for(int j=i;j<=n;j++)   sum[i][j]=sum[i][j-1]+a[j];//计算前缀和 
    dp();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值