您的位置:首页 > 编程语言 > C语言/C++

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

2016-09-15 10:00 225 查看
【问题描述】


  有如下一个双人游戏:

  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]
<<" "<<sum[1]
-d[1]
;
}

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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Cpp dp