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 }
View Code
posted @ 2013-08-12 21:31  小菜刷题史  阅读(399)  评论(0编辑  收藏  举报