HDU1024-最大子段和

1.题目链接。题意大概就是,把一个数组划分成不相交的m段,使得这m段之和加起来最大。输出最大值。这一看肯定是个DP了,我么稍微的分析一下其实就可以写出DP式子。

2.dp[i][j]表示前j个数划分成i段的最大值,显然我们这里的决策取决于j是否属于第i段。那么就会有两种情况(1)第j个元素被划分在了第i段,这个转移就很简单了dp[i][j]=d[i][j-1]+a[j].这个转移应该是很显而易见的吧。就不多说了。那么第二种情况呢?(2)第j个元素没有被划分在第i段内,那么这里就有点难以理解了。既然第j个元素没有被划分在第i段内,说明前j-1个元素被划分成了i-1段。那么这里就有一个问题,被划分成i-1段至少需要i-1个元素,最多j-1个元素。(因为当前只有j-1个元素可用)。所以这里并不是简单的dp[i][j]=dp[i-1][j-1]+a[j],需要枚举从i-1开始,到j-1结束这些状态,到底多少个元素划分成i-1段是最大的。所以这里的dp式子应该是这样写:dp[i][j]=max(dp[i-1][k]+a[j])&&i-1<=k<=j-1.综合起来的DP式就是这样的:

                                                           DP[i][j]=max(DP[i][j-1]+a[j],max(DP[i-1][k]+a[i]));

看上去这题已经结束了,然而一看数据范围就知道凉凉。m的范围没有给,n是1e6.这个DP时间复杂度是n3方,空间只要是m大一点点就爆内存。所以,这个式子没用。要找到优化的方法。

3.优化:其实,DP[i][j]中的第二维似乎没什么用,为什么?,因为不需要知道这么多的中间信息,这样说好像也是不太对。或者这样说吧,其实最关心的是DP[i],也就是n个元素被划分成m段的最大值。重新来写DP式,DP[i]表示m个元素被划分成i段的最大值,这个转移是取决于a[j]的。这个先不管,先看DP式最里面的那个max(DP[i-1][k]+a[k]),这个式子就是在遍历找上一个状态写被划分成i-1段之后的最大值。那么其实很简单了,这个操作没必要做一次,在上一次处理的时候把它记下来。专门使用一个数组记录,这样就可以直接查到。这样就优化了一层for循环。时间复杂度下降了一个维度。再来看空间,尝试着采用一维数组来改写这个DP式子。DP[i]就表示m个数字被划分成i段最大值。这样其实就会有:dp[i]=max(dp[i-1]+a[i],dp2[i-1]+a[i])).然后再加上我们需要维护的一些信息,更新即可。代码如下:

#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 1e6+10;
int dp1[maxn];//这个记录当前的最大值
int a[maxn];//数据存放的地方
int dp2[maxn];//这个记录上一个的最大值
int tem = 0;
const int INF = 1e9;
#pragma warning(disable:4996)
int main()
{
	int n, m;
	while (~scanf("%d%d", &m, &n))
	{
		for (int i = 1; i <= n; i++)
		{
			scanf("%d", &a[i]);
			dp1[i] = 0;
			dp2[i] = 0;
		}
		//初始化
		for (int i = 1; i <= m; i++)
		{
			tem = -INF;
			for (int j = i; j <= n; j++)
			{
				dp1[j] = max(dp1[j - 1] + a[j], dp2[j - 1] + a[j]);
				dp2[j - 1] = tem;
				tem = max(tem, dp1[j]);
			}
		}
		cout << tem << endl;
	}
}

 

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值