【问题描述】
有如下一个双人游戏:
N个正整数的序列放在一个游戏平台上,游戏由玩家1开始,两人轮流从序列的两端取数,取数后该数字被去掉并累加到本玩家的得分中,当数取尽时,游戏结束。以最终得分多者为胜。
编一个执行最优策略的程序,最优策略就是使自己能得到在当前情况下最大的可能的总分的策略。你的程序要始终为两位玩家执行最优策略。
【输入格式】
第一行: 正整数N, 表示序列中正整数的个数。
第二行至末尾: 用空格分隔的N个正整数(大小为1-200)。
【输出格式】
只有一行,用空格分隔的两个整数: 依次为玩家一和玩家二最终的得分。
【输入样例】
6
4 7 2 9 5 2
【输出样例】
18 11
【数据范围】
2 <= N <= 100
【传送矩阵】
【思路梳理】
思考一下经典的一道区间型动态规划问题:
取数游戏:
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;
}