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