Given a consecutive number sequence S1, S2, S3, S4 ... Sx, ... Sn (1 ≤ x ≤ n ≤ 1,000,000, -32768 ≤ Sx ≤ 32767). We define a function sum(i, j) = Si + ... + Sj (1 ≤ i ≤ j ≤ n).
Now given an integer m (m > 0), your task is to find m pairs of i and j which make sum(i1, j1) + sum(i2, j2) + sum(i3, j3) + ... + sum(im, jm) maximal (ix ≤ iy ≤ jx or ix≤ jy ≤ jx is not allowed).
But I`m lazy, I don't want to write a special-judge module, so you don't have to output m pairs of i and j, just output the maximal summation of sum(ix, jx)(1 ≤ x ≤ m) instead. ^_^
Process to the end of file.
該題是經典的動態規劃問題(m段子和的最大值),這道題我查閱了大概三四篇博客才完全搞定之,優化之後的算法堪稱美妙絕倫!
首先,動態規劃的老步驟,我們用dp[i][j]來表示問題的一種狀態,定義如下
dp[i][j]: 以j結尾的i段子和的最大值
那麼狀態轉移方程爲
dp[i][j] = max{dp[i][j-1] + a[j], max{dp[i-1][t] (i-1<=t <= j-1) + a[j]}}
簡要解釋一下,在求dp[i][j]之前,假定dp[i][j-1]已經求得,即以j-1結尾的i段子和的最大值已知,那麼在求以j結尾的i段子和的最大值的時候,首先j肯定是在第i段,這時,j只有兩種情況,j要麼自成一段(max{dp[i-1][t] (i-1<=t <= j-1) + a[j]),要麼和前面j-1個的數中的末尾幾個數成一段(dp[i][j-1] + a[j]),這個狀態轉移方程應該不難理解
狀態轉移方程可以進一步優化
dp[i][j] = max{dp[i][j-1] , max{dp[i-1][t] (i-1<=t <= j-1) }} + a[j];
max裏面還有max,比較討厭,因此我們將它單獨拎出來,令w[i][j] = max{dp[i][t](i<=t <=j)}
因此max{dp[i-1][t] (i-1<=t <= j-1) } = w[i-1][j-1]
因此
dp[i][j] = max{dp[i][j-1], w[i-1][j-1]}
w[i][j] = max{dp[i][t](i<=t <=j)} = max{dp[i][i], dp[i][i+1], ....,dp[i][j-1], dp[i][j]} = max{w[i][j-1], dp[i][j]}
最後的解爲w[m][n]
至此,我們得到了兩個關鍵的狀態轉移方程,並且,我們注意到每次求解dp[i][j]的時候,不會用到dp[i-1][...]的信息,因此,我們可以將此數組退化爲一維數組,優化之後狀態轉移方程爲
dp[j] = max{dp[j-1], w[i-1][j-1]}
w[i][j] = max{w[i][j-1], dp[j]}
仔細觀察w數組,再次發現每一次求解dp數組和w數組,只用到了w數組的兩行信息,因此,我們可以使用滾動數組繼續優化w數組,將w退化成2xn數組
假定當前循環到第i層,用t來表示求解w第i行的值,1-t來表示上一次求得的w數組的信息,那麼,我們初始化t爲1,就能使得w的行數在0-1之間變化,w,見下面代碼
int t = 1;
for (int i = 1; i <= m; ++i) {
w[t][i] = dp[i] = sum[i];
for (int j = i+1; j <= n; ++j) {
dp[j] = max(dp[j-1], w[1-t][j-1]) + a[j];
w[t][j] = max(dp[j], w[t][j-1]);
}
t = 1 - t; //這裏表示爲將此次求的的w數組成爲下一次求w數組的前面一行,也就是i-1行
}
最後,注意邊界條件 w[0][i] = 0;不難寫出一下AC代碼
#include <iostream>
#define MAX 1000010
using namespace std;
int w[2][MAX]
int dp[MAX];
int a[MAX];
int sum[MAX];
inline int max(int a, int b) {
return a > b ? a : b;
}
int main() {
int m, n, c;
while (scanf("%d%d", &m, &n) > 0) {
sum[0] = 0;
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
sum[i] = sum[i-1] + a[i];
w[0][i] = 0;
}
int t = 1;
for (int i = 1; i <= m; ++i) {
w[t][i] = dp[i] = sum[i];
for (int j = i+1; j <= n-m+i; ++j) {
dp[j] = max(dp[j-1], w[1-t][j-1]) + a[j];
w[t][j] = max(dp[j], w[t][j-1]);
}
t = 1 - t;
}
printf("%d\n", w[m%2][n]); //由於w退化了,沒了w[m][n]因此m%2可以表示第m次,你可以用m = 1,和2試試便知道了
}
return 0;
}
最後,關於j的循環終止值的優化作一點說明:
如果求解m-1段子和的子和,只需要求解到n-1個數
因爲第n個數會在i的下一次循環中求到
那麼如果求解m-2段子和的子和,只需要求解到n-2個數
。。。
求解m-(m-i) = i段子和的子和,只需要求解到n-(m-i) = n-m+i個數
貌似這個只可意會,反正我覺得我有點說不清啊