poj1050
最大子矩阵和问题是由数组最大连续子段和引出来的。
有关最大连续子段和问题的动态规划解一般人都情况,这里就不在累述。
首先明确一段关于矩阵最大子矩阵和问题
用例子来说明一目了然
给定以下的一个4×4的矩阵(这里以方阵来解释)
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
这个方阵的最大子矩阵为
9 2
-4 1
-1 8
-4 1
-1 8
和为15
问题描述:给定一个矩阵,求这个矩阵的最大的子矩阵和
解法1:累加法
这种方法对矩阵的每个元素进行累加,首先求出以(1,1)到(i,j)区间的子矩阵的和,用二维数组sum[i][j]表示。
有了上面的基础,现在我们可以求出矩阵中任意子矩阵的和,假设我们要求子矩阵,起点为(i1,j1),终点为(i2,j2)的子矩阵的和,我们只需用sum[i][j]来解决。sum = sum[i2][j2] - sum[i2-1][j1] - sum[i1-1][j2] + sum[i1-1][j1-1];
对于这题,我们只需枚举子矩阵的起点和终点就行,保存和最大的那个
代码如下:
Memory: 244K | Time: 63MS | |
Language: C++ | Result: Accepted |
- Source Code
-
#include <stdio.h> #include <string.h> #define N 101 int arr[N][N];int sum[N][N]; int main(void){ //printf("%d\n", 0xffffffff); int i, j; int n; while (scanf("%d", &n) != EOF) { for (i = 1; i <= n; i++) { for (j = 1; j <= n; j++) { scanf("%d", &arr[i][j]); sum[i][j] = 0; } } sum[0][0] = 0; for (i = 1; i <= n; i++) { sum[i][0] = 0; sum[0][i] = 0; } for (i = 1; i <= n; i++) for (j = 1; j <= n; j++) sum[i][j] = sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1] + arr[i][j]; int sti; int stj; int edi; int edj; int max = 0x80000000; for (sti = 1; sti <= n; sti++) { for (stj = 1; stj <= n; stj++) { for (edi = sti; edi <= n; edi++) { for (edj = stj; edj <= n; edj++) { int s = sum[edi][edj] - sum[edi][stj-1] - sum[sti-1][edj] + sum[sti-1][stj-1]; if (s > max) max = s; } } } } printf("%d\n", max); } return 0;}
解法2:转化为数组的最大子段和
这个想法我没想到,网上找了人家的解题报告看的,大体思路是将几行的同一列的元素值加起来,组成一个新一维数组,然后求这个一维数组的最大子段和,这个思路真是好,将未知的题型向已知转化,我怎么就想不到!!!
剩下的事就是枚举矩阵的起始行和终止行了。
代码如下
Memory: 204K | Time: 47MS | |
Language: C++ | Result: Accepted |
- Source Code
-
#include <stdio.h>#include <string.h> #define N 100 int arr[N][N];int sum[N]; int MaxSubSequenceSum(int *a, int s, int e){ int Sum = 1 << 31; int ret = Sum; int i = s; while (i < e) { if (Sum < 0) Sum = 0; Sum += a[i++]; if (Sum > ret) ret = Sum; } return ret;} int main(void){ int i, j; int n; while (scanf("%d", &n) != EOF) { for (i = 0; i < n; i++) { for (j = 0; j < n; j++) { scanf("%d", &arr[i][j]); } } int sti; int edi; int max = 0x80000000; for (sti = 0; sti < n; sti++) { for (edi = sti; edi < n; edi++) { memset(sum, 0, sizeof(sum)); for (j = 0; j < n; j++) { for (i = sti; i <= edi; i++) { sum[j] += arr[i][j]; } } int sub_sum = MaxSubSequenceSum(sum, 0, n); if (sub_sum > max) max = sub_sum; } } printf("%d\n", max); } return 0; }
改进
上面的解法2其实时间复杂度不比解法1好到那里去,因为每次还得计算起始行到终止行的数组和,所以时间复杂度还是O(n^4),有没有办法减少计算子矩阵和的复杂度呢,我们最先的想法是先求出所有的字段和,保存起来,但是由于保存所有这些子段需要耗费一些空间复杂度,所以我们可以借鉴解法1中的求法,只是这里不需要求所有的sum,而只须求从第1行累加到第i行的数组即可。到时候要用起始从i1行,终止在i2行之间的数组,我们只需用sum[i2]-[sumi1-1]即可。
Memory: 244K Time: 0MS Language: C++ Result: Accepted - Source Code
-
#include <stdio.h>#include <string.h> #define N 100 int arr[N][N];int sum[N];int sum_block[N][N]; int MaxSubSequenceSum(int *a, int s, int e){ int Sum = 1 << 31; int ret = Sum; int i = s; while (i < e) { if (Sum < 0) Sum = 0; Sum += a[i++]; if (Sum > ret) ret = Sum; } return ret;} int main(void){ int i, j; int n; while (scanf("%d", &n) != EOF) { for (i = 0; i < n; i++) { for (j = 0; j < n; j++) { scanf("%d", &arr[i][j]); } } //memset(sum_block, 0, sizeof(sum_block)); //sum_b for (i = 0; i < n; i++) { for (j = 0; j < n; j++) { if (i == 0) sum_block[i][j] = arr[i][j]; else sum_block[i][j] = sum_block[i-1][j] + arr[i][j]; } } int sti; int edi; int max = 0x80000000; for (sti = 0; sti < n; sti++) { for (edi = sti; edi < n; edi++) { memset(sum, 0, sizeof(sum)); for (j = 0; j < n; j++) { if (sti == 0) sum[j] = sum_block[edi][j]; else sum[j] = sum_block[edi][j] - sum_block[sti-1][j]; } int sub_sum = MaxSubSequenceSum(sum, 0, n); if (sub_sum > max) max = sub_sum; } } printf("%d\n", max); } return 0; }