poj2479 Maximum sum 线性dp+最大子段和改版

题意:对数组a,要求找到两段不重合的子段,使得两子段和最大     /  /每个子段至少有一个元素.  T=30,n=5e4

 

一般的,联想到两种思路:

1.根据已知的o(n)求最大子段和的方式(hdu1003),推出此题的解法

2.定义一个新状态,用dp求解

因为是两段子段和,因此不好定义新的状态dp求解,故用第一种思路.

 

hdu1003中,状态dp[i]是以a[i]为最尾端元素的最大子段和,同样的,我们可以从这个角度出发.

那么两个子段和与一个子段和如何转换?我们容易想到几种思路:

1.先找一个以a[i]结尾的最优子段和,然后在后面再找一个最优子段和(否定,后面那个有可能包含前面一个,不好处理)

2.找到总序列的最优子段,然后找到断点进行"切割"(否定,如1 -100 1 2 3就不能这样处理,而且找最小子段和的算法不好写)

3.找一个a[i]为结尾的最优子段和,再找一个以a[j]为起点的最优子段和(也可以理解为逆序数组找a[i]结尾,定义为dpp),二者相加(可行)

 

用方法3,可是先确定i,再确定j,找到最大的解,似乎要o(n^2)的复杂度来暴力,该如何解决?

其实可以用这样的思路:如果已经确定a[i],也就是确定了dp[i,]那么就是要从dpp[i+1]~dpp[n-1]找到最大的那个数,

其实,我们完全可以用数组r[i],记录dpp[i]~dpp[n-1]之中的最大值,而求出这个数组,会不会超时呢?

答案是不会的,因为求r时,可以运用递推的思想,

求r[i],dpp[i]~dpp[n-1]的最值,也就是求子问题:

dpp[i]和dpp[i+1]~dpp[n-1]的最值,得到状态转移方程r[i]=max(dpp[i],r[i+1]),问题就这么得到了解决.

 

一开始写的时候,我写了一个l数组,和r数组,然后遍历每个分隔点,根据l和r数组求解,这样要更费时间和空间一点:

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <stack>
#include <queue>
#include <map>
#include <set>
#include <vector>
using namespace std;
typedef long long ll;
const int maxn=500010;
const int inf=0x3f3f3f3f; 
int t,n,cnt;
int a[maxn],dp[maxn],dpp[maxn],dp_[maxn],dpp_[maxn];

int main()
{
	scanf("%d",&t);
	while(t--){
		memset(dp,0,sizeof(dp));//应该也不用全为0,首尾就可以了 
		memset(dpp,0,sizeof(dpp));
		memset(dp_,0,sizeof(dp_));
		memset(dpp_,0,sizeof(dpp_));	
		scanf("%d",&n);
		for(int i=0;i<n;i++) scanf("%d",&a[i]);
		dp[0]=a[0],dpp[n-1]=a[n-1];
		for(int i=1;i<n;i++){
			if(dp[i-1]>0){
				dp[i]=dp[i-1]+a[i];
			}else {
				dp[i]=a[i];
			}
		}
		for(int i=n-2;i>=0;i--){
			if(dpp[i+1]>0){
				dpp[i]=dpp[i+1]+a[i];
			}else {
				dpp[i]=a[i];
			}
		}
		dp_[0]=dp[0];
		for(int i=1;i<n;i++)
			dp_[i]=max(dp[i],dp_[i-1]);
		dpp_[n-2]=dpp[n-1];
		for(int i=n-3;i>=0;i--)
			dpp_[i]=max(dpp_[i+1],dpp[i+1]);
			
		int ans=dp_[0]+dpp_[0];
		for(int i=1;i<n-1;i++){
			ans=max(ans,dp_[i]+dpp_[i]);
		}
		printf("%d\n",ans);
	}
	return 0;	
} 




/*

1
7
2 2 3 -3 4 -4 5

2 4 7 4 8 4 9
9 7 5 2 5 1 5


2 4 7 7 8 8   9
7 5 5 5 5 5   5

1
6
2 -100  1 2 3 4
12

1
2
1 -10
-9
*/



 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值