动态规划 III——D - Max Sequence

在正式解答这道题之前,我要首先先提出另一个和本题有关的基础题,理解了这道基础题,本题会更加容易理解。

一、最大子序列和问题(大概意思就是:给出一个有序序列,让你求出一段“和最大”的连续子序列)

如何理解?设想一下,所求的连续子序列肯定以某个元素结束,求出所有的“以某个元素结束的连续子序列的最大和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


其中,rb数组与b数组作用类似,只不过rb[j]保存的是“从尾到头,以a[j]元素为结束元素的连续子序列的和的最大值 ”,rMaxSum同样只需根据rb数组即可求出。

代码

下面是以"动态规划 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);
	}
}



后话:以上借鉴了点击打开链接这篇博客,以及综合自己的理解来写的解题报告。如有疑惑或者不对之处,期待你的提问或者指正。所谓温故而知新,日后我若对题目有新的理

解和感悟,也会随之改进以上文章的内容。


















  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值