最大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;
}