杭電OJ——1024 Max Sum Plus Plus 詳細分析+優化全過程

Problem Description
Now I think you have got an AC in Ignatius.L's "Max Sum" problem. To be a brave ACMer, we always challenge ourselves to more difficult problems. Now you are faced with a more difficult problem.

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. ^_^
 

Input
Each test case will begin with two integers m and n, followed by n integers S1, S2, S3 ... Sn.
Process to the end of file.
 

Output
Output the maximal summation described above in one line.
 

Sample Input
1 3 1 2 3 2 6 -1 4 -2 3 -2 3
 

Sample Output
6 8
Hint
Huge input, scanf and dynamic programming is recommended.
 



該題是經典的動態規劃問題(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個數

貌似這個只可意會,反正我覺得我有點說不清啊

發佈了140 篇原創文章 · 獲贊 70 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章