-
4 -1 0 100 2
样例输出 99
描述
Little Hi and Little Ho are playing a game. There is an integer array in front of them. They take turns (Little Ho goes first) to select a number from either the beginning or the end of the array. The number will be added to the selecter's score and then be removed from the array.
Given the array what is the maximum score Little Ho can get? Note that Little Hi is smart and he always uses the optimal strategy.
输入
The first line contains an integer N denoting the length of the array. (1 ≤ N ≤ 1000)
The second line contains N integers A1, A2, ... AN, denoting the array. (-1000 ≤ Ai ≤ 1000)
输出
Output the maximum score Little Ho can get.
题意:ho,hi轮流取数,可以取数列的头或尾,每次取一个,取完的数从数列中剔除,问ho先手,ho所能拿到的数最大的和是?
官方题解:
每次轮到小Hi或者小Ho时,他们都面临同样的二选一决策:是拿走最左边的数,还是拿走最右边的数?
当然可以暴搜。时间复杂度O(2^N)会超时。此外比较容易发现贪心策略——总是拿走更大的数——是错误的。样例就是范例。
其实本题是一道非常经典的动态规划题目。
由于每次只能从数组首/尾拿走一个数,所以小Hi和小Ho在任何时候面对的残局都只可能是一段连续的子数组A[i..j]。我们不妨用f[i][j]表示当面对A[i..j],先手最多能获得的得分。
如果我们能计算出所有f[i][j]的值,那么显然f[1][n]就是最终答案。
其中i = j的情况f[i]j的值是很容易计算的:f[i][j]=A[i]。因为只剩下A[i]一个数,先手只能拿走A[i]。
对于i < j的情况,先手P需要决策是拿走A[i]还是拿走A[j]?
如果拿走A[i],那么对手Q面对的是A[i+1 .. j],Q最多能获得的得分是f[i+1][j]。而且Q一定会按照得到f[i+1][j]这个得分的方式进行决策。所以先手P最大得分是sum(A[i .. j]) - f[i+1][j]。(A[i][j]的分数和减去P的得分)
同理如果拿走A[j],先手P最大得分会是sum(A[i .. j]) - f[i][j-1]。
由于此时先手P可以二选一,所以f[i][j] = max{ sum(A[i .. j]) - f[i+1][j], sum(A[i .. j]) - f[i][j-1] } = sum(A[i .. j]) - min(f[i+1][j], f[i][j-1])。
注意sum(A[i .. j])可以通过预处理出前缀和再O(1)计算得出。
区间DP模板题:
区间动态规划问题一般都是考虑,对于每段区间,他们的最优值都是由几段更小区间的最优值得到,是分治思想的一种应用,将一个区间问题不断划分为更小的区间直至一个元素组成的区间,枚举他们的组合 ,求合并后的最优值。
设F[i,j](1<=i<=j<=n)表示区间[i,j]内的数字相加的最小代价
最小区间F[i,i]=0(一个数字无法合并,∴代价为0)
每次用变量k(i<=k<=j-1)将区间分为[i,k]和[k+1,j]两段
For l:=1 to n do // l是区间长度,作为阶段。
for i:=1 to n do // i是穷举的区间的起点
begin
j:=i+l-1; // j是 区间的终点,这样所有的区间就穷举完毕
if j>n then break; // 这个if很关键。
for k:= i to j-1 do // 状态转移,去推出 f[i,j]
f[i , j]= max{f[ i,k]+ f[k+1,j]+ w[i,j] }
end;
这个结构必须记好,这是区间动态规划的代码结构。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN =1000+10;
int a[MAXN],sum[MAXN];
int dp[MAXN][MAXN]; //dp[i][j]表示在a[i...j]区间内先手所能得到的最大分数
int main()
{
int n;
scanf("%d",&n);
sum[0]=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
sum[i]+=sum[i-1]+a[i];
}
int j;
for(int l=1;l<=n;l++) //区间长度
{
for(int i=1;i<=n;i++) //区间起点
{
j=i+l-1;
if(j>n)break;
if(i==j) dp[i][j]=a[i];
else
{
dp[i][j]=max(sum[j]-sum[i-1]-dp[i+1][j],sum[j]-sum[i-1]-dp[i][j-1]);
}
}
}
printf("%d\n",dp[1][n]);
return 0;
}