数组两端取数之和最大
题目描述
一个整数数组,两个人一次分别从左边或者右边拿走一个数,两个人足够聪明,求第一个人拿到数的最大和。
解题思路
首先想到的是贪心法,每次都取两端中的最大的数,但是很显然这是错的。例如以下的测试用例:
2 6 8 3
贪心法会得到结果9但是正确答案应该是10。而贪心法没法解决全局最优的时候,我们一般就会采用动态规划来解决。
动态规划最重要的就是状态的表示和状态转移方程,那么根据数组的特点我们比较容易想到使用dp[i][j]来表示数组下标为i到j(i<=j)的元素做游戏得到的最优解。而如果dp[i][j]代表了A选手在i到j元素上的最优解,那么A选手下一步要么取走i元素,要么取走j元素。
由于B选手也是每次选择最优的解,那么dp[i+1][j]或者dp[i][j-1]就代表了选手B在i到j元素上的最优解(因为A取了一个数之后剩下的元素要么是i+1到j,要么是i到j-1)。
再考虑到i到j的数组之和是固定的,那么A的得分加上B的得分就等于i到j的元素之和,A的得分就等于这个固定值减去B的得分,因此A要想得分最高,就等价于使得B的得分最低。
因此状态转移方程如下:
// sum(i,j)表示下标为i到j的元素之和
dp[i][j] = sum(i,j) - min(dp[i-1][j], dp[i][j-1]);
有了状态转移方程,就不难写出程序了。
AC代码
#include <iostream>
using namespace std;
int main()
{
int N; cin>>N;
int data[N];
for(int i=0;i<N;++i) cin>>data[i];
int dp[N][N];
// 初始化,如果只有一个元素,最优就是直接取该元素
for(int i=0;i<N;++i){
dp[i][i] = data[i];
}
// res[i][j]表示i到j的元素之和
int res[N][N];
for(int i=0;i<N;++i){
res[i][i] = data[i];
for(int j=i+1;j<N;++j){
res[i][j] = res[i][j-1]+data[j];
}
}
// 数组从小到大开始dp
for(int d=1;d<N;++d){
for(int i=0;i+d<N;++i){
dp[i][i+d] = res[i][i+d] - min(dp[i][i+d-1], dp[i+1][i+d]);
}
}
cout<<dp[0][N-1]<<endl;
return 0;
}