POJ1050 To the Max
题目来源:http://poj.org/problem?id=1050
题目大意:
给出一个N*N的整数方阵,求它的一个子矩阵,使得其元素之和最大。
例如4*4的方阵A:
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
左上角的子阵:
9 2
-4 1
-1 8
元素之和15为最大。
输入:第一行为一个不大于100的整数N,后接N*N个整数,分别用空格、换行或空行隔开。每个整数都在范围[-127,127]之间。
输出:子阵元素和的最大值。
Sample Input
4 0 -2 -7 0 9 2 -6 2 -4 1 -4 1 -1 8 0 -2
Sample Output
15
一道颇为经典的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)。
1 ////////////////////////////////////////////////////////////////////////// 2 // POJ1050 To the Max 3 // Memory: 244K Time: 16MS 4 // Language: C++ Result: Accepted 5 ////////////////////////////////////////////////////////////////////////// 6 7 #include <cstdio> 8 9 using namespace std; 10 11 int N; 12 int data[100][100]; 13 int c[101][100]; 14 int max_sum = -127; 15 16 int main(void) { 17 scanf("%d", &N); 18 for (int i = 0; i < N; ++i) { 19 for (int j = 0; j < N; ++j) { 20 scanf("%d", &data[i][j]); 21 } 22 } 23 24 //c[i][j]表示sum(data[0][j], data[1][j].. ,data[i - 1][j]), 即第i列前j个元素之和 25 for (int i = 1; i <= N; ++i) { 26 for (int j = 0; j < N; ++j) { 27 c[i][j] = c[i - 1][j] + data[i - 1][j]; 28 } 29 } 30 int a, b = 0; 31 for (int i1 = 1; i1 <= N; ++i1) { 32 for (int i2 = 1; i2 <= i1; ++i2) { 33 b = 0; //b为一行中以某元素为尾的子段的最大和 34 for (int j = 0; j < N; ++j) { 35 a = c[i1][j] - c[i2 - 1][j]; //第j列i1-1行至i2-1行元素之和 36 if (b > 0) { 37 b += a; //之前字段和与当前元素值之和 38 } else { 39 b = a; //前面的子段和不大于0,舍弃 40 } 41 max_sum = b > max_sum ? b : max_sum; 42 } 43 } 44 } 45 printf("%d", max_sum); 46 return 0; 47 }