最大M划分子段和
最大子段和的变种
N个整数组成的序列a[1],a[2],a[3],…,a[n],将这N个数划分为互不相交的M个子段,
并且这M个子段的和是最大的。如果M >= N个数中正数的个数,那么输出所有正数的和。
首先构造递推式$dp[i][j]$代表i个数的j划分并且以a[i]结尾。
则可知:
$
dp[i][j]=max{dp[i-1][j],max{dp[k][j-1]}}+a[i] (j-1<=k<i) $
第一项$dp[i-1][j]+a[i]$代表将$a[i]$和前面的元素共同划为第J个划分
第二项$dp[k][j-1]+a[i]$代表把$a[i]$单独划为一项。
由以上递推式可得:
#include <cstdio> using namespace std; const int maxn = 5000; int a[maxn]; const int inf = -(1 << 20); int dp[maxn][maxn]; int main() { int n, m; scanf("%d%d", &n, &m); long long sumP = 0; int p = 0; for (int i = 1; i <= n; i++) { scanf("%d", &a[i]); if (a[i] > 0) { ++p; sumP += a[i]; } } if (m >= p) { printf("%lld\n", sumP); } else { for (int j = 1; j <= m; j++) { for (int i = j; i <= n; i++) { dp[i][j] = dp[i - 1][j] + a[i]; for (int k = j - 1; k < i; k++) { if (dp[i][j] < dp[k][j - 1] + a[i]) dp[i][j] = dp[k][j - 1] + a[i]; } } } printf("%d\n", dp[n][m]); } }
|
然而时间复杂度不慎理想$O(mnn)$肯定是不行的。
仔细观察第二项时间主要耗费在寻找$max{dp[k][j-1]}$上怎么优化呢,dp!!
我们再用一个数组$f[i][j]$代表$i$个数$j$划分的最优方案。
可得以下递推式:
$dp[i][j]=max {dp[i-1][j],f[i-1][j-1]}+a[i]$
$f[i][j]=max{dp[i][j],f[i-1][j]}
$
根据以上递推式可得:
#include <algorithm> #include <cstdio>
using namespace std; const int maxn = 5000; int a[maxn]; const int inf = -(1 << 20); int dp[maxn][maxn]; int f[maxn][maxn]; int main() { int n, m; scanf("%d%d", &n, &m); long long sumP = 0; int p = 0; for (int i = 1; i <= n; i++) { scanf("%d", &a[i]); if (a[i] > 0) { ++p; sumP += a[i]; } } if (m >= p) { printf("%lld\n", sumP); } else { for (int j = 1; j <= m; j++) { for (int i = j; i <= n; ++i) { dp[i][j] = max(dp[i - 1][j], f[i - 1][j-1]) + a[i]; f[i][j] = max(f[i - 1][j], dp[i][j]); } } printf("%d\n", f[n][m]); } }
|
分析时间复杂度可知变为$O(mn)$可以接受,然而空间复杂度变为$2O(m*n)$,变为原来的二倍。仔细观察发现递推式只与相邻的两项有关,可以利用滚动数组来优化。
将原来的数组第二维优化为2。
#include <algorithm> #include <cstdio>
using namespace std; const int maxn = 5000+2; int a[maxn]; const int inf = -(1 << 20); long long dp[maxn][2]; long long f[maxn][2]; int main() { int n, m; scanf("%d%d", &n, &m); long long sumP = 0; int p = 0; for (int i = 1; i <= n; i++) { scanf("%d", &a[i]); if (a[i] > 0) { ++p; sumP += a[i]; } } if (m >= p) { printf("%lld\n", sumP); } else { int t = 0, g = 0; for (int j = 1; j <= m; j++) { t = (j & 1); g = 1 - t; dp[j - 1][t] = inf; for (int i = j; i <= n; ++i) { dp[i][t] = max(dp[i - 1][t], f[i - 1][g]) + a[i]; f[i][t] = max(f[i - 1][t], dp[i][t]); } } printf("%lld\n", f[n][t]); } }
|
至此,最大M划分子段和成功解决。