最大子段和问题及其推广

实在是太颓废了,所以开始写点东西吧。。。。

最大子段和: 给定N个数(可能为负整数)组成的序列a1...an,求该序列形如∑ak(i<=k<=j)的子段和的最大值。当所有整数均为负整数时定义其最大的子段和为0.

依据题意我们可以确定 最优值为max(0,max∑ak(i<=i<=j<=n)); 若我们令b[j] = max∑ak(1<=i<=j)是从序列a的第i个数到第j个数的最大和,那么我们最后的答案就是找出b数组里面记录的最大的那个值就是答案。由b[j]的定义可以知道b[j-1]>0时,b[j] = b[j-1]+a[j]否则b[j] = a[j];由此得出递推式b[j] = max(b[j-1]+a[j],a[j]) (1<=j<=n)

代码如下

 1 int Maxsum1(int n,int *a)
 2 {
 3     int sum = 0,b = 0;
 4     for(int i=1;i<=n;i++){
 5         if(b>0) b += a[i];
 6         else b = a[i];
 7         if(b > sum ) sum = b;
 8     }
 9     return  sum;
10 }
View Code

最大子段和的推广

(1)最大子矩阵:给定一个m行n列的整数矩阵A,求其元素和最大的子矩阵

很明显最大子矩阵和的问题是最大子段和的问题向二维的推广,我们用数组a[1:m][1:n]表示给定的矩阵A 其子数组a[i1:i2][j1:j2]表示左上角和右下角的行列坐标分别为(i1,j1)(i2,j2)的子矩阵 其个各个元素之和 记为 S(i1,i2,j1,j2)= ∑∑a[i][j] (i1<=i<=i2,j1<=j<=j2) 那么ans = Max S(i1,i2,j1,j2) (1<=i1<=i2<=m,1<=j1<=j2<=n)

我们可以令t(i1,i2) = maxS(i1,i2,j1,j2)(i<=j1<=j2<=n) = Max∑∑a[i][j] 同样我们令b[j] = ∑a[i][j](i1<=i<=i2) 于是 t(i1,i2) = max∑b[j](1<=j1<=j2<=n) 这又回到了一维的情况于是我们可以得到如下代码

 1 int Maxsum2(int m,int n,int a[105][105] )
 2 {
 3     int sum = 0;
 4     int b[105];
 5     for(int i=1;i<=m;i++){
 6         memset(b,0,sizeof(b));
 7         for(int j=i;j<=m;j++){
 8             for(int k=1;k<=n;k++) b[k] += a[j][k];
 9             int Max = Maxsum1(n,b);
10             if(Max > sum) sum = Max;
11         }
12     }
13     return sum ;
14 }
View Code

最大子矩阵在poj上面有原题目 题号是1050 代码如下

#include <cstdio>
#include <cstring>
#include <iostream>

using namespace std;

const int M = 105;

int a[M][M];

int Maxsum1(int n,int *a)
{
    int sum = 0,b = 0;
    for(int i=1;i<=n;i++){
        if(b>0) b += a[i];
        else b = a[i];
        if(b > sum ) sum = b;
    }
    return  sum;
}

int Maxsum2(int m,int n,int a[105][105] )
{
    int sum = 0;
    int b[105];
    for(int i=1;i<=m;i++){
        memset(b,0,sizeof(b));
        for(int j=i;j<=m;j++){
            for(int k=1;k<=n;k++) b[k] += a[j][k];
            int Max = Maxsum1(n,b);
            if(Max > sum) sum = Max;
        }
    }
    return sum ;
}

int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++)
            scanf("%d",&a[i][j]);
    }
    printf("%d\n",Maxsum2(n,n,a));




    return 0;
}
View Code

 2 最大M子段和

最大子段和是最大M子段和的特殊情况即M = 1时的解,我们用b(i,j)表示数组a的前j项中i个子段和的最大值 那么很容易得到如下状态转移方程 b(i,j) = max(b(i,j-1),max(b(i-1,t))+a[j] 第一个状态表示第i个子段包含a[j],而第二个状态表示第i个子段只包含a[j] 于是很容易得到如下代码

 1 int MaxSum()
 2 {
 3     memset(b,0,sizeof(b));
 4     for(int i=1;i<=m;i++){
 5         for(int j=i;j<=n-m+i;j++){
 6             if(j>i){
 7                 b[i][j] = b[i][j-1]+a[j];
 8                 for(int k=i-1;k<j;k++){
 9                     b[i][j] = max(b[i][j],b[i-1][k]+a[j]);
10                 }
11 
12             }
13             else
14                 b[i][j] = b[i-1][j-1] + a[j];
15 
16         }
17     }
18     int sum = 0;
19     for(int j=m;j<=n;j++){
20         if(sum < b[m][j]) sum = b[m][j];
21     }
22     return sum ;
23 }
View Code

上述算法显然需要O(mn^2)但是我们注意到 对b[i][j] 只用到了 第i-1行和第i行因此只要记录这两个值就可以,另一方面max(b(i-1,t))的值可以在计算第i-1行是预先计算出来

于是得到优化后的代码如下

int dp()
{


    b[0] = 0;
    c[1] = 0;

    for(int i=1;i<=m;i++){
        b[i] = b[i-1] + a[i];
        c[i-1] = b[i];
        int tmp = b[i];
        for(int j=i+1;j<=n-m+i;j++){
            b[j] = max(b[j-1],c[j-1])+a[j];
            c[j-1] = tmp;
            if(tmp < b[j]) tmp = b[j];

        }

        c[i+n-m] = tmp;

    }
    printf("%d\n",c[n]);

}
View Code

最大M子段和问题在杭电OJ上有原题 题号是1024 

AC代码如下

#include <cstdio>
#include <iostream>
#include <cstring>

using namespace std;

const int M = 1000005;

int n,m;
int a[M];
int b[M],c[M];


int dp()
{


    b[0] = 0;
    c[1] = 0;

    for(int i=1;i<=m;i++){
        b[i] = b[i-1] + a[i];
        c[i-1] = b[i];
        int tmp = b[i];
        for(int j=i+1;j<=n-m+i;j++){
            b[j] = max(b[j-1],c[j-1])+a[j];
            c[j-1] = tmp;
            if(tmp < b[j]) tmp = b[j];

        }

        c[i+n-m] = tmp;

    }
    printf("%d\n",c[n]);

}
int main ()
{
    while(scanf("%d%d",&m,&n)!=EOF){
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
            dp();
            memset(c,0,sizeof(c));
    }


    return 0;
}
View Code

 

posted @ 2015-07-12 14:14  lmlyzxiao  阅读(220)  评论(0编辑  收藏  举报