hdu1024 最大子和段 dp+滚动数组

DP算法优化实践
本文探讨了一个经典的DP(动态规划)问题,即将一个数组分为多个段以求得最大和。通过逐步优化,从最初的O(n^3)复杂度降低至O(n^2),并实现了空间优化。文章详细解释了优化过程,包括使用滚动数组和记录前一状态最大值的方法。

刚开始刷dp,先来几个水题
题意:把一个长度为n的数组分成不重合的m段,求最大和
思路:显然的dp题
一开始的思路就是,一个二维数组dp[i][j],表示选第j个数并把a[1,j]分成i段
那么就要想这个dp[i][j]是如何来的,可以由dp[i][j-1], 即前j-1个分成i段并且选了a[j-1]这个数,所以可以直接加上去,还有一种情况是可以a[j]自成一段,那么dp[i][j]就等于max(dp[i-1][k])+a[j], 1<=k<=j-1,前j-1个分成i-1段加上a[j]自成一段,那么状态转移方程就出来了
状态转移方程为
dp[i][j]=max(dp[i][j-1], dp[i-1][k])+a[j];
核心代码如下:

while(~scanf("%d%d", &m, &n)){
		for(int i = 1; i <= n; i++) a[i]=read();
		for(int i = 1; i <= m; i++){
			for(int j = i; j <= n; j++){
				ll mx = -inf;
				for(int k = 1; k < j; k++){
					mx = max(mx, dp[i-1][k]);
				}
				dp[i][j] = max(dp[i][j-1], mx)+a[j];
			}
		}
		ll res = -inf;
		for(int i = m; i <= n; i++) res = max(res, dp[m][i]);
		printf("%lld\n", res);
	}	

但是可以注意到n的范围到1e6, m范围不清楚,这种O(n^3)的做法肯定会tle,而且内存也会爆掉,所以进行优化, 我们可以另外再开一个数组来存储max(dp[i-1][k]),进行时间上的优化,同时可以发现dp可以变为滚动数组,变成一维,这样就变成了O(n^ 2) (姑且算成是n^2), 而且不会爆掉内存。
那么具体如何实现呢,首先是对空间的优化,可以看出dp其实要用到的只是dp[i][ * ]和dp[i-1][ * ]对这两个状态进行记录即可
对时间的优化即是dp[i-1][ * ]求最大值 不需要考虑具体的位置k,只要在j-1处记录最大值即可。

#pragma GCC optimize("Ofast","inline","-ffast-math")
#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<bits/stdc++.h> 
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
#define rep(i, a, n) for(int i = a; i <= n; i++)
#define per(i, a, n) for(int i = n; i >= a; i--)
#define IOS std::ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define fopen freopen("file.in","r",stdin);freopen("file.out","w",stdout);
#define fclose fclose(stdin);fclose(stdout);
const int inf = 1e9;
const ll onf = 1e18;
const int maxn = 1e6+10;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
	while (isdigit(ch)){x=(x<<3)+(x<<1)+ch-48;ch=getchar();}
	return x*f;
}
ll mx[maxn], dp[maxn], a[maxn];
signed main(){
	int n, m;
	while(~scanf("%d%d", &m, &n)){
		rep(i,1,n) a[i]=read();
		ll tmp = 0;		
		rep(i,1,n) mx[i]=dp[i]=0;	//初始化
		rep(i,1,m){
			tmp = -inf;
			rep(j,i,n){
				dp[j] = max(dp[j-1], mx[j-1])+a[j];
				// for(int k = 1; k < j; k++){
				// 	mx = max(mx, dp[i-1][k]);
				// }
				mx[j-1] = tmp;	//这里用mx[j-1]来进行记录,tmp为dp[i][j-1]的最大值
				// dp[i][j] = max(dp[i][j-1], mx)+a[j];
				tmp = max(dp[j], tmp); // 对tmp进行更新
			}
		}
		printf("%lld\n",tmp );
	}	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值