二维前缀和之最大的和

时间:2021-07-13 23:23:19

参考《算法竞赛进阶指南》p46 To The Max

参考链接:https://www.acwing.com/problem/content/description/128/

       https://www.cnblogs.com/GodA/p/5237061.html

       https://blog.csdn.net/yzyyylx/article/details/78298318

在一个二维矩阵中求前缀和,可以快速的计算出子矩阵中的和。首先预处理处以所有点为右下角,(1,1)为左上角的矩阵中的元素和. 
接着(x1,y1)为右下角,(x2,y2)为左上角的矩形中的元素和为f[x1][y1]+f[x2-1][y2-1]-f[x1][y2-1]-f[x2-1][y1].

二维前缀和之最大的和

如上矩阵中。

#include<bits/stdc++.h>
#define MAXM 3010
#define MAXN 3010
using namespace std;

int m,n,a[MAXM][MAXN],jx[MAXM][MAXN],x,y,u,v;

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

    //预处理: 
    for(i=1;i<=m;i++)
    {
        for(j=1;j<=n;j++)
        {
            jx[i][j]=a[i][j]+jx[i-1][j]+jx[i][j-1]-jx[i-1][j-1];
        }
    }

    while(~scanf("%d%d%d%d",&u,&v,&x,&y))
    {
        printf("%d\n",jx[u][v]+jx[x-1][y-1]-jx[u][y-1]-jx[x-1][v]);
    }
}
/*
5 5
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19 20
21 22 23 24 25
1 1
0 0
1
2 2
1 1
16
4 4
3 3
64
 */

 

输入右下角和左上角的值就能够算出子矩阵的和

题目大意:就是输入一个N*N的矩阵,找出在矩阵中,所有元素加起来之和最大的子矩阵。

  例如在    0 -2 -7 0 这样一个4*4的矩阵中,元素之和最大的子矩阵为   9 2  ,它们之和为15。 

       9  2 -6 2                        -4 1
      -4 1 -4 1                         -1 8
      -1 8 0 -2

 

  这是一个最大子矩阵问题,我们怎么来解决这个问题呢?任何问题都会有它的简化的问题,这是二维的数组,与之对应的,我们可以先尝试一下一维数组。

  如果有一个一维数组a[n],如何找出连续的一段,使其元素之和最大呢?

  例如有 1 2 -3 4 -2 5 -3 -1 7 4 -6 这样一个数组,那么显然 4 -2 5 -3 -1 7 4 这个子数组元素之和最大,为4+(-2)+5+(-3)+(-3)+7+4=14。为找到一维数组的最大子数组,我们可以有以下方法。

动态规划:我们来分析一下最优子结构,若想找到n个数的最大子段和,那么要找到n-1个数的最大子段和,这就出来了。我们用b[i]来表示a[0]...a[i]的最大子段和,b[i]无非有两种情况:

(1)最大子段一直连续到a[i]  

(2)以a[i]为首的新的子段。

由此我们可以得到b[i]的状态转移方程:b[i]=max{b[i-1]+a[i],a[i]}。最终我们得到的最大子段和为max{b[i], 0<=i<n}, 算法如下:

int MaxSubArray(int a[],int n)
{
    int i,b = 0,sum = 0;
    for(i = 0;i < n;i++)
    {
        if(b>0)                // 若a[i]+b[i-1]会减小
            b += a[i];        // 则以a[i]为首另起一个子段
        else    
            b = a[i];
        if(b > sum)    
            sum = b;
    }
    return sum;
}

回到最大子矩阵问题,我们固定矩阵的左右边界,利用二维前缀和很容易求出夹在左右边界的小矩阵的值。代码如下;

#include<bits/stdc++.h>
#define MAXM 3010
#define MAXN 3010
using namespace std;

int m,n,a[MAXM][MAXN],jx[MAXM][MAXN];

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

    //预处理:
    for(i=1;i<=n;i++)
    {
        for(j=1;j<=n;j++)
        {
            jx[i][j]=a[i][j]+jx[i-1][j]+jx[i][j-1]-jx[i-1][j-1];
        }
    }

    int res=-10000;
    for (int i = 1; i <=n; ++i) {
        for (int j = i; j <=n; ++j) {
            int last=0;
            for (int k = 0; k < n; ++k) {
                if(last>0)
                    last+=jx[j][k]+jx[i-1][k-1]-jx[j][k-1]-jx[i-1][k];
                else
                    last=jx[j][k]+jx[i-1][k-1]-jx[j][k-1]-jx[i-1][k];
               // last=max(last,0)+jx[j][k]+jx[i-1][k-1]-jx[j][k-1]-jx[i-1][k];
                res=max(last,res);
            }

        }
    }
    cout<<res;
}