1. 程式人生 > >最大m子段和總結與例題 51nod1052 HDU1024

最大m子段和總結與例題 51nod1052 HDU1024

最大m子段和

一、定義

給定由n個整數(可能為負)組成的序列a1、a2、a3...,an,

以及一個正整數m,要求確定序列的m個不相交子段,使這m個子段的總和最大!

特別注意:

有些題目可能不存在負數答案,給出的序列全是負數,那麼不管m是多少,答案是0。此時選擇的子段是0個,不足m個,但符合題意。。。

也可能有些題目要求,必須選夠m個子段。

區別在dp陣列的初始化。前者要求dp初始為0,後者要求第0行為0,其餘為負無窮

二、解題思路

動態規劃,藉助矩陣可以直觀的看到計算過程。 定義二維陣列dp, dp[ i ][ j ],表示前 j 項所構成 i 子段的最大和,且必須包含著第j項,即以第j項結尾
然後是一個遞推過程。 求dp[ i ][ j ],有兩種情況 1、dp[ i ][ j ] = dp[ i ] [ j-1 ] + a[ j ] ,即把第j項融合到第 j-1 項的子段中,子段數沒變 2、dp[ i ][ j ] = dp[ i-1 ] [ t ] + a[ j ],(i-1<= t < j )
把第 j 項作為單獨的一個子段,然後找一下i-1個子段時,最大的和,然後加上a[ j ] 
然後比較上面兩種情況,取大的。 下面看圖,紅色數字為輸入的序列:
如圖,要求dp[ 3 ][ 6 ],只需比較他左邊的那個,和上面那一行圈起來的之中最大
的數,
再加上a[ j ] 即為dp[ 3 ][ 6 ] 的值。 優化一下: 1、沿著第m行的最後一個元素,往左上方向畫一條線,線右上方的元素是沒必要計算的
那麼dp[ i ][ j ] ,j++的時候,j的上限為 i + n - m 即可。 還有左下角那一半矩陣,也是不用計算的,因為1個數字不可能分為2個子段
2、每確定一個dp[ i ][ j ],只需用到本行和上一行,所以不用開維陣列也可以,省記憶體。
開兩個一維陣列,pre和dp,pre記錄上一行,dp記錄當前行

3、再對上一行紅圈中的數字找最大值時,若用一個迴圈來找,容易超時。

優化方法:在每次計算dp之前,同時記錄下j前面的最大元素。

時間複雜度大致為O(m*(n-m+1)),mn-m方 通過圖片,分析情況1和2,就能發現,從左上角走到第 m 行的最後一個元素即可,找出第 m 行的最大值即為答案。 詳見例題。

三、例題

1、51nod 1052 基準時間限制:2 秒 空間限制:131072 KB 分值: 80 難度:5級演算法題 N個整陣列成的序列a[1],a[2],a[3],…,a[n],將這N個數劃分為互不相交的M個子段,並且這M個子段的和是最大的。如果M >= N個數中正數的個數,那麼輸出所有正數的和。 例如:-2 11 -4 13 -5 -2,分為2段,11 -4 13一段,6一段,和為26。 Input
第1行:2個數N和M,中間用空格分隔。N為整數的個數,M為劃分為多少段。(2 <= N , M <= 5000)
第2 - N+1行:N個整數 (-10^9 <= a[i] <= 10^9)
Output
輸出這個最大和
Input示例
7 2
-2
11
-4
13
-5
6
-2
Output示例
26

【AC程式碼】:

#include<bits/stdc++.h>
using namespace std;  
typedef long long ll;
const int N=1e6+5; 
const int INF=0x3f3f3f3f;
int n,m;
ll a[N],dp[2][N];   //只儲存上一行和當前行 
int main()  
{
    while(~scanf("%d%d",&n,&m))   //n個數字,m子段和 
    { 
        for(int i=1;i<=n;i++) 
			scanf("%lld",a+i);  
		for(int i=0;i<=n;i++)
			dp[0][i]=0,dp[1][i]=0;    //關鍵!此題答案只允許正值
		
        for(int i=1,k=1;i<=m;i++,k^=1)    //分為i段,k為兩行之間的切換
        {
        	dp[k][i-1]=-INF;   //i==j時,杜絕與前一元素共伍
            ll maxpre=-INF;    //maxpre記錄上一行的最大值
            for(int j=i;j<=n-m+i;j++)
            {
                maxpre=max(maxpre,dp[k^1][j-1]);     //隨時更新上一行最大值
                dp[k][j]=max(dp[k][j-1],maxpre)+a[j]; //*對情況1、2的選擇
            }
        }
        ll ans=-INF;
        for(int i=m;i<=n;i++)   //找到第m行的最大值,即為答案
                ans=max(ans,dp[m&1][i]);
        printf("%lld\n",ans);
    }  
}

2、HDU 1024 http://acm.hdu.edu.cn/showproblem.php?pid=1024

 資料量比51nod的這個題大,一百萬。上面的程式碼,把n和m互換就可以過HDU1024