7-1 最大子列和问题 (20分)
7-1 最大子列和问题 (20分)
给定\(K\)个整数组成的序列\({N_1, N_2, ..., N_K}\),“连续子列”被定义为\({ N_i, N_{i+1}, ..., N_j}\),其中\(1≤i≤j≤K\)。“最大子列和”则被定义为所有连续子列元素的和中最大者。例如给定序列{ -2, 11, -4, 13, -5, -2 },其连续子列{ 11, -4, 13 }有最大的和20。现要求你编写程序,计算给定整数序列的最大子列和。
本题旨在测试各种不同的算法在各种数据情况下的表现。各组测试数据特点如下:
数据1:与样例等价,测试基本正确性;
数据2:102个随机整数;
数据3:103个随机整数;
数据4:104个随机整数;
数据5:105个随机整数;
输入格式:
输入第1行给出正整数\(K (≤100000)\);第2行给出\(K\)个整数,其间以空格分隔。
输出格式:
在一行中输出最大子列和。如果序列中所有整数皆为负数,则输出0。
输入样例:
6
-2 11 -4 13 -5 -2
输出样例:
20
思路
这是一道动态规划的题目。
先来个dp数组,是一维的还是二维的?
首先,不读完整个序列,无法确定连续最大子列。
- 尝试递归
在读入第i个数,就确定了,直到i的连续最大子列,和也知道。
那么读入第i+1个数。既然是最大,那么负数一定要排除。所以第i个数若是负数(情况1),此时必定不是最大子列的元素。
第i个数若是正数,也不一定是最大子列的元素,因为可能,最大子列吸收它的代价太高。
看第i+1个数,(情况1),如果能够补偿i的损失。
(情况2),能够弥补吸收的代价。
失败。 - 尝试递推
这里的底层元素,显然就是每一个数本身。
性质分类就是 + - 。
如果i以后的数和为正,那么最大子列会吸收后续所有
最大连续子列
9:15 2019/1/23 第二天思考。
dp数组应该是一维的。
对dp[i]先做初始化,在开头遇到的负数,其dp都为0
遇到第一个正数,及之后的所有数,dp为其本身。
/*这是主要方程,但不是全部*/
dp[i] = dp[i+1]+a[i];
10:34 2019/1/23 成功AC,对状态转移方程做了详细的定义。
分析了4种情况。我的想法是让人“未卜先知”,或者说是看到未来,所以是从最后一位数的dp来思考的。dp记录的是最大连续子列吸收该数的后果。要做到这一点,必须已经读入整个序列,从最后一个数开始考虑。因为随着后续数的不同,对某个数吸收与否的命运会改变。
如果已经知道吸收一个数的后果,就可以判断是否应该吸收了。
同时,吸收该数,希望吸收最小的坏后果,所以要对(a[i]>0 && dp[i+1]<0)
特判,这种情况下,最优策略是仅仅吸收该整数本身。
AC代码
#include<bits/stdc++.h>
using namespace std;
int main(void){
int n,i=0,sum=0;
cin>>n;
vector<int> dp(n),a(n);
/*初始化dp数组*/
bool flag = false;/*代表遇到正数以后*/
while(cin>>a[i]){/*调试时,需要ctrl+z*/
if(a[i]>0){
flag =true;
dp[i] = a[i];
}
else if(flag){
dp[i] = a[i];
}
else{
dp[i] = 0;
}
i++;
}
// for(int i =0;i<n;i++){
// cout<<a[i]<<endl;
// cout<<dp[i]<<endl;
// }
/*成功读入*/
/*状态转移方程*/
for(int i=n-2;i>=0;i--){
if(a[i]>0 && dp[i+1]<0){
continue;
}
else
dp[i] = dp[i+1] + a[i];
}
cout<<dp[0];
return 0;
}
参考胡凡的书,这里对状态转移方程可以优化一波。
for(int i=n-2;i>=0;i--){
dp[i] = max(a[i],dp[i+1] + a[i]);
}
我的状态转移方程也是满足无后效性的。
从头开始的动态规划,就是定义了dp
数组元素dp[i]
为以a[i]结尾的连续子列最大和。
dp[i] = max(a[i],dp[i-1] + a[i]);
对最大和条件做了弱化,或者说是定义修改。
接着找出,这些和中值最大的那一个。