在正式解答这道题之前,我要首先先提出另一个和本题有关的基础题,理解了这道基础题,本题会更加容易理解。
一、最大子序列和问题(大概意思就是:给出一个有序序列,让你求出一段“和最大”的连续子序列)
如何理解?设想一下,所求的连续子序列肯定以某个元素结束,求出所有的“以某个元素结束的连续子序列的最大和b[j]”,再在所有的b[j]中取出最大值。
(举例:有一个序列3,-4,2,10,求以连续子序列的最大和。如何思考?
(1)以a[1]=3结束的子序列只有一个:{3}。所以“以a[1]结束的连续子序列的最大和"为b[1]=3。
(2)以a[2]=-4结束的子序列有两个:{3,-4},{-4}。所以“以a[2]结束的连续子序列的最大和"为b[2]=-1。
(3)以a[3]=2结束的子序列有两个:{3,-4,2},{-4,2},{2}。所以“以a[3]结束的连续子序列的最大和"为b[3]=2。
(4)以a[4]=10结束的子序列有两个:{3,-4,2,10},{-4,2,10},{2,10},{10}。所以“以a[4]结束的连续子序列的最大和"为b[4]=12。故
最大和=max(b[1],b[2],b[3],[4])。)
实际上,以a[j-1]为结束元素的连续子序列最大和b[j-1],这个序列序尾肯定就是a[j-1]。那么,当b[j-1]>0时,无论
a[j-1]紧后相邻的a[j]为何值,b[j]=b[j-1]+a[j]就是以a[j]为结束元素的连续子序列最大和。用数学思想理解这句话,你就
能得出下面的结论。
下面求b[j]。
(1)当b[j-1]>0时,无论a[j]为何值,b[j]=b[j-1]+a[j];
(2)当b[j-1]<=0时,无论a[j]为何值,b[j]=a[j];
例子
k | 1 | 2 | 3 | 4 |
a[k] | 3 | -4 | 2 | 10 |
b[k] | 3 | -1 | 2 | 12 |
其中,b[1]=a[1],b[2]=b[1]+a[2],b[3]=b[3],b[4]=b[3]+b[4]。因此,对序列{3,-4,2,10},最大连续子序列和为12。
//代码
int b[n+1];
int b[1]=a[1];
for(int i=2;i<=n;i++)
{
if(b[i-1]<=0)
b[i]=a[i];
else
b[i]=b[i-1]+a[i];
}
int ans=b[1];
for(int i=1;i<=n;i++)
ans=max(ans,b[i]);
//简洁版:
int b[n+1];
int b[1]=a[1];
int ans=b[1];
for(int i=2;i<=n;i++)
{
b[i]=max(b[i-1]+a[i],a[i]);//动态规划的状态转移方程,其实就是由“if...else...”语句合并的“max或min”语句。
ans=max(ans,b[i]);
}
算法时间复杂度为O(n)。
二、最大子序列和问题的变种
1.两个不重叠(可相邻)的连续子序列的最大和(大概意思就是,给出一个有序序列,让你找出两个连续的子序列,要求1:两个连续的子序列没有共同元素;要求2:它们的和是最大的)
问题定义
设数组a[t],1<=t<=n,两个不重叠连续子序列的最大和定义为:
问题分析
应用了求最大子序列和的方法。其求解算法如下:
(1)从头到尾扫描一遍数组,其循环下标i从1增加到n,依次求得子数组a[1...i]的最大子段和,将结果保存在maxSum[1...n]数组;
(2)从尾到头扫描一遍数组,其循环下标i从n减到1,依次求得子数组a[i...n]的最大子段和,将结果保存在rMaxSum[1...n]数组;
(3)从尾到头扫描一遍数组(其实哪个方向无所谓),其循环下标从n-1到1(不含n,因为n后面没有子序列与之匹对),求和maxSm[i]+rMaxSum[i+1],取最大的结果,即max{maxSum[i]+rMaxSum[i+1]},1<=i<=n-1,即为所要求的结果。
例子
t | 1 | 2 | 3 | 4 |
a[t] | 3 | -4 | 2 | 10 |
b[t] | 3 | -1 | 2 | 12 |
maxSum[t] | 3 | 3 | 3 | 12 |
rb[t] | 11 | 8 | 12 | 10 |
rMaxSum[t] | 12 | 12 | 12 | 10 |
代码
下面是以"动态规划 III——D - Max Sequence“为例给出相应的代码。
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
using namespace std;
const int maxn=100000+10;
int a[maxn];
int b[maxn],rb[maxn];
int maxSum[maxn],rMaxSum[maxn];
int main()
{
int N;
while(scanf("%d",&N))
{
if(!N)
break;
for(int i=1;i<=N;i++)
scanf("%d",&a[i]);
memset(b,0,sizeof(b));
memset(rb,0,sizeof(rb));
memset(maxSum,0,sizeof(maxSum));
memset(rMaxSum,0,sizeof(rMaxSum));
b[1]=a[1];
maxSum[1]=b[1];
for(int i=2;i<=N;i++) //从头到尾扫描一遍数组,其循环下标i从1增加到n,依次求得子数组a[1...i]的最大子段和
{
if(b[i-1]<0)
b[i]=a[i];
else
b[i]=b[i-1]+a[i];
}
for(int i=2;i<=N;i++)
maxSum[i]=max(maxSum[i-1],b[i]); //将结果保存在maxSum[1...n]数组
rb[N]=a[N];
rMaxSum[N]=rb[N];
for(int i=N-1;i>=1;i--) //从尾到头扫描一遍数组,其循环下标i从n减到1,依次求得子数组a[i...n]的最大子段和
{
if(rb[i+1]<0)
rb[i]=a[i];
else
rb[i]=rb[i+1]+a[i];
}
for(int i=N-1;i>=1;i--)
rMaxSum[i]=max(rMaxSum[i+1],rb[i]); //将结果保存在rMaxSum[1...n]数组
int ans=maxSum[1]+rMaxSum[1+1]; //万一全是负数呢,别漏了这种情况。
for(int i=2;i<=N-1;i++)
ans=max(ans,maxSum[i]+rMaxSum[i+1]);
printf("%d\n",ans);
}
}
//再来一个简洁版:
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
using namespace std;
const int maxn=100000+10;
int a[maxn];
int b[maxn],rb[maxn];
int maxSum[maxn],rMaxSum[maxn];
int main()
{
int N;
while(scanf("%d",&N))
{
if(!N)
break;
for(int i=1;i<=N;i++)
scanf("%d",&a[i]);
memset(b,0,sizeof(b));
memset(rb,0,sizeof(rb));
memset(maxSum,0,sizeof(maxSum));
memset(rMaxSum,0,sizeof(rMaxSum));
b[1]=a[1];
maxSum[1]=b[1];
for(int i=2;i<=N;i++)
{
b[i]=max(a[i],b[i-1]+a[i]); //从头到尾扫描一遍数组,其循环下标i从1增加到n,依次求得子数组a[1...i]的最大子段和
maxSum[i]=max(maxSum[i-1],b[i]); //将结果保存在maxSum[1...n]数组
}
rb[N]=a[N];
rMaxSum[N]=rb[N];
for(int i=N-1;i>=1;i--)
{
rb[i]=(a[i],rb[i+1]+a[i]); //从尾到头扫描一遍数组,其循环下标i从n减到1,依次求得子数组a[i...n]的最大子段和
rMaxSum[i]=max(rMaxSum[i+1],rb[i]); //将结果保存在rMaxSum[1...n]数组
}
int ans=maxSum[1]+rMaxSum[1+1]; //万一全是负数呢,别漏了这种情况。
for(int i=2;i<=N-1;i++)
ans=max(ans,maxSum[i]+rMaxSum[i+1]);
printf("%d\n",ans);
}
}
后话:以上借鉴了点击打开链接这篇博客,以及综合自己的理解来写的解题报告。如有疑惑或者不对之处,期待你的提问或者指正。所谓温故而知新,日后我若对题目有新的理
解和感悟,也会随之改进以上文章的内容。