1. 程式人生 > >Max Sum Plus Plus 最大子段和 經典 Dp

Max Sum Plus Plus 最大子段和 經典 Dp

kuangbin專題連結:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=68966#problem/A

題意:最大m子段問題:求給定無交叉的區間數目,求出最大和值。

看到,n達到1000000,心裡第一想法就是隻能是一維Dp問題,可是,在推導的狀態轉移公式時,還得用二維Dp思想。

看到網上的演算法時間複雜度為O(mn),看來m不是很大。

其實,我們很容易想到一個狀態轉移方程:dp[i][j]:表示在第i個位置處,區間數為j的最大和值。

則有兩種轉移情況:

一:dp[i][j]中的第i個位置,不屬於dp[i][j]中的最後一個子段j(值等於子段數目),則 dp[i][j]=dp[i-1][j];

二:dp[i][j]中的第i個位置屬於dp[i][j]中的最後一個子段j(值等於子段數目),此時,我們又容易想到:最後一個子段的終點一定,列舉最後一個子段的起點即可,狀態轉移方程為:dp[i][j]=max(dp[k][j-1]+s[i]-s[k]),其中s表示字首和,1<k<i。剛開始我就是這樣想的,無奈做不出來。看題解:此時,又把最後一個位置j分成兩種情況:

1:dp[i][j]的最後一個位置i屬於dp[i-1][j]的最後一個子段j,即:相當於把位置i放入dp[i-1][j]的尾部,則:dp[i][j]=dp[i-1][j]+a[i]。

2:dp[i][j]的最後一個位置i不屬於dp[i-1][j]的最後一個子段j,即:最後一個位置屬於一個獨立的子段,則:dp[i][j]=dp[k][j-1]+a[i]。1<=k<i;

至此轉移方程就出來了,但是n是在太大了,開一個二位陣列不太妥。說要用滾動陣列,其實我對滾動陣列還是沒怎麼會用,參考別人的程式碼,然後寫了一遍。

//freopen("C:\\Documents and Settings\\All Users\\桌面\\in.txt","r",stdin);

#include<iostream>
#include<cstdio>
#include<sstream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<set>
#include<queue>
#include<vector>
#include<map>
#include<string>
#define LL __int64
#define INF 0x7fffffff

using namespace std;

int dp[1000010],p[1000010],a[1000010],m,n;

int main(){
    while(cin>>m)
    {
        cin>>n;
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        memset(dp,0,sizeof(dp)),memset(p,0,sizeof(p));
        int Max;
        for(int i=1;i<=m;i++){
            Max=-INF;
            for(int j=i;j<=n;j++)//很顯然,j必須>=i
            //滾動陣列就是及時回收利用後面用不到的空間,這裡是p陣列
                dp[j]=max(dp[j-1],p[j-1])+a[j],p[j-1]=Max,Max=max(dp[j],Max);
        }
        cout<<Max<<endl;
    }
    return 0;
}