最大M子段和
题意描述:给定数组a,长度为n。给定整数m,求不相交的m段字段和的最大值。
当m == 1 时:该问题就是最大子段和问题。
设dp[i]为以a[i]结尾的最大子段和,当我们考虑dp[i]的时候如果dp[i-1] > 0那么肯定把a[i]接在后面最优,否则,取a[i]最优。
得到 dp[i] = max(dp[i-1]+a[i],a[i]);
这样的话,问题是不是就明朗了呢?
int MAX = 0;
for(int i = 1; i <= n; i++)
{
dp[i] = max(dp[i-1]+a[i],a[i]);
if(dp[i] > MAX) MAX = dp[i];
}
容易看出来dp[i] 只和dp[i-1] 有关,因此记录一个last变量表示dp[i-1] 就能推出dp[i],所以并不需要开数组的。
int last = 0,MAX = 0;
for(int i = 1; i <= n; i++)
{
last = max(last+a[i],a[i]);
if(last > MAX) MAX = dp[i];
}
若m不为1,那么考虑给dp再加一维,表示段数。类比一维的表述:
dp[i][j] 表示以a[j]结尾的i子段和的最优值。
考虑第j个元素:
若我们把a[j]接在在最后面一段里,dp[i][j]可以表述为:x1 = dp[i][j-1]+a[j];
若我们让a[j]自成一段,dp[i][j]可以表述为: x2 = max(dp[i-1][k])+a[j], 其中i-1 =< k <= j-1;
得到: dp[i][j] = max(x1,x2);
int ans = 0;
memset(dp,0,sizeof(dp);
for(int i = 1; i <= m; i++)
{
for(int j = i; j <= n; j++)
{
dp[i][j] = dp[i][j-1] + a[j];
for(int k = j-1; k >= i-1; k--)
{
dp[i][j] = max(dp[i][j],dp[i-1][k]+a[j]);
}
if(i == m) ans = max(ans,dp[i][j]);
}
}
容易看出来 时间复杂度 O(m*n^2) 空间复杂度 O(m*n)
从上面容易看出来,其实在求dp[i][j] 时只用到了第i层与第i-1层,因此考虑用last数组存上一层,dp存当前层减少空间开销。
又注意到,其实第三层for循环其实是在求last数组前几项的最大值,因此可以直接处理好。
继续优化:
ll ans = 0;
memset(dp,0,sizeof(dp));
memset(last,0,sizeof(last));
for(int i = 1; i <= m; i++)
{
for(int j = i; j <= n; j++)
{
dp[j] = dp[j-1] + a[j];
dp[j] = max(dp[j],last[j-1]+a[j]);
if(i == m) ans = max(ans,dp[j]);
}
for(int j = i; j <= n; j++)
{
last[j] = max(last[j-1],dp[j]);
}
}
容易看出来 时间复杂度 O(m*n) 空间复杂度 O(n)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn = 5005;
const ll inf = 1e18;
ll dp[maxn],last[maxn];
ll a[maxn],b[maxn];
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i = 1; i <= n; i++)
{
scanf("%lld",&a[i]);
}
ll ans = 0;
memset(dp,0,sizeof(dp));
memset(last,0,sizeof(last));
for(int i = 1; i <= m; i++)
{
for(int j = i; j <= n; j++)
{
dp[j] = dp[j-1] + a[j];
dp[j] = max(dp[j],last[j-1]+a[j]);
if(i == m) ans = max(ans,dp[j]);
}
for(int j = i; j <= n; j++)
{
last[j] = max(last[j-1],dp[j]);
}
}
cout << ans << endl;
return 0;
}