最大m段子段和

动态规划,借助矩阵可以直观的看到计算过程。
定义二维数组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
 
1052 最大M子段和
基准时间限制:2 秒 空间限制:131072 KB 分值: 80 难度:5级算法题
 
 

N个整数组成的序列a[1],a[2],a[3],…,a[n],将这N个数划分为互不相交的M个子段,并且这M个子段的和是最大的。如果M
 >= N个数中正数的个数,那么输出所有正数的和。
例如:-2 11 -4 13 -5 6 -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
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<map> 
#include <math.h>
#include<bits/stdc++.h> 
using namespace std;
typedef long long ll; 
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
const int INF=0x3f3f3f3f;
const int maxn=1e6+100;
int n,m;
int a[maxn];
int dp[maxn];
int cmax[maxn];
int main(){
    int n,m,Max;
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    for(int i=1;i<=m;i++){
        Max=-INF;
        for(int j=i;j<=n;j++){
            dp[j]=max(dp[j-1]+a[j],cmax[j-1]+a[j]);
            cmax[j-1]=Max;
            Max=max(Max,dp[j]); 
        }
    }
    printf("%d",Max); 
}

例题2:

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

问题描述

现在我想你们已经了解伊格那丢了。L的“最大和”问题。为了做一个勇敢的演员,我们总是挑战自己去解决更困难的问题。现在你面临一个更困难的问题。

 

给定一个连续的数字序列S1, S2, S3, S4…Sx,……Sn(1≤x≤n≤1,000,000,-32768≤Sx≤32767)。我们定义了一个函数sum(i, j) = Si +…+ Sj(1≤i≤j≤n)

 

现在给定一个整数m(m > 0),你的任务是找到m对i和j,它们组成和(i1, j1) +和(i2, j2) +和(i3, j3) +…+和(im, jm)最大(ix≤iy≤jx或ix≤jy≤jx不允许)。

 

但是我很懒,我不想写一个特殊的判断模块,所以你不必输出m对I和j,只输出和(ix, jx)(1≤x≤m)的最大和。^ _ ^

 

 

输入

每个测试用例将以两个整数m和n开始,然后是n个整数S1, S2, S3…Sn。

处理到文件末尾。

 

 

输出

在一行中输出上面描述的最大总和。

 

 

样例输入

1 3 1 2 3

2 6 -1 4 -2 3 -2 3

 

 

样例输出

6

8

 

提示

 

建议使用大输入、scanf和动态编程。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<map> 
#include <math.h>
#include<bits/stdc++.h> 
using namespace std;
typedef long long ll; 
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
const int INF=0x3f3f3f3f;
const int maxn=1e6+100;
int n,m;
int a[maxn];
int dp[maxn];
int cmax[maxn];
int main(){
    int n,m;
    int Max;
    while(~scanf("%d%d",&m,&n)){
        memset(dp,0,sizeof(dp));
        memset(cmax,0,sizeof(cmax));
        for(int i=1;i<=n;i++){
            cin>>a[i];
        }
        for(int i=1;i<=m;i++){
            Max=-INF;
            for(int j=i;j<=n;j++){
                dp[j]=max(dp[j-1]+a[j],cmax[j-1]+a[j]);//cmax是上一步的    
                cmax[j-1]=Max;//为i++后准备,而不是j++,,    
                Max=max(Max,dp[j]);
                 
            }
        }
        printf("%d
",Max);
    }
}
#include <iostream>

#include <cstdio>

#include <cstring>

#include <algorithm>

using namespace std;

const int maxn = 1e6+10;

const int INF = 0x7fffffff;

int a[maxn];

int dp[maxn];

int Max[maxn];//max(dp[i-1][k])就是上一组0~j-1的最大值

int main(){

    int n,m,mmax;

    while(~scanf("%d%d",&m,&n)){

        for(int i = 1; i <= n; i++){

            scanf("%d",&a[i]);

        }

        memset(dp,0,sizeof(dp));

        memset(Max,0,sizeof(Max));//分成0组的全是0

        for(int i = 1; i <= m; i++){//分成i组

            mmax = -INF;

            for(int j = i; j <= n; j++){//前j个数分成i组,至少需要i个数

                dp[j] = max(dp[j-1]+a[j],Max[j-1]+a[j]);

                //Max[j-1]目前代表的是分成i-1组前j-1个数的最大值,a[j]单独一组组成i组

                //dp[j-1]代表j-1个数分成组,第j个数a[j]放在前面i组的一组中,两种方式选取较大者

                Max[j-1] = mmax;//当前考虑的是j但是mmax是上一次循环得到的,所以更新的是j-1

                mmax = max(mmax,dp[j]);//更新mmax,这样下次循环同样更新的是j-1

            }

            //这样也就更新得到了分i组的Max,下次分i+1组的时候就可以使用了

        }

        printf("%d
",mmax);

    }

    return 0;

}

参考:

https://blog.csdn.net/winter2121/article/details/72848482

https://blog.csdn.net/onion___/article/details/79157809?depth_1.utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

原文地址:https://www.cnblogs.com/lipu123/p/12347675.html