一道颇为经典的DP题。
首先,先看一个比该问题更简单更基础的问题:最大子序列和,或者叫最大子串和,连续子序列最大和等等。
问题很简答:给出一个一维数组A,求它的一个子数组,使得数组元素的和最大。
比如:给定数组A{5,-3,4,2},那么它的最大子序列为{5,-3,4,2},和为8,而{5,-6,4,2}的最大子序列是{4,2},和为6.仔细看这两个列子,我们会发现,找最大子序列的方法其实很简单:遍历原数组,假设当前扫描到第 i 个元素a ,假设以第 i-1 个元素为结尾的子序列和最大值b大于0,那么我们可以继续向后扫描,累加元素。反之,如果b小于0,那么如果把前面的串继续向后扩展,得到的和会比直接从a开始的子串小,所以应该把前面的串舍弃。同时,我们还应该记下每次算出的子序列和,如果比当前最大和大,则更新它。因为有可能出现所有元素都为负数的情况,所以最大和应该初始化为元素值的下限而不是0。
实例:
data: 1 -2 3 10 -4 7 2 -5
b: 0 1 -1 3 13 9 16 18 13
max: -127 1 1 3 13 13 16 18 18
有了上面这个复杂度为O(n)的求一维数组最大子序列和的算法后,最大子矩阵和的问题就可以转化为这个问题从而得到解决了。首先是如何进行转化:一个矩阵的元素和,等于该矩阵中每一列的元素和再求和(相当于先把矩阵纵向压成一个数组,再把这个数组横向压成一个总和)。假设用c[i][j]表示方阵第 j 列前 i 行的元素和,那么c[i1][j] - c[i2 - 1][j]就可以表示第 j 列从第 i2 行到第 i1 行的元素之和。这样,对于每个行号 i1 和 i2,我们可以计算出每列在这两行之间的列元素和,这样就构成了一个一维数组,再对这个数组用上述最大子序列和算法,就可以得到由i2、i1确定上下边界的所有子阵的最大和了。由此,只需对所有的 i2<=i1 进行上述计算,记录找到的最大值即可。复杂度O(n^3)。
AC代码:
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
#include<stdio.h> #include<string.h> #include<algorithm> using namespace std; #define N 1005 int a[N][N]; int c[N][N]; int main() { int n; while(scanf("%d",&n)!=EOF) { int i,j,k; for(i=0;i<n;i++) { for(j=0;j<n;j++) { scanf("%d",&a[i][j]); } } memset(c,0,sizeof(c)); for(i=1;i<=n;i++) { for(j=0;j<n;j++) { c[i][j]=c[i-1][j]+a[i-1][j]; } } int t,sum=0; int M=0; for(i=1;i<=n;i++) { for(j=1;j<=i;j++) { sum=0; for(k=0;k<n;k++) { t=c[i][k]-c[j][k]; if(sum>0) { sum+=t; } else { sum=t; } M=max(sum,M); } } } printf("%d\n",M); } return 0; }