在DP问题中有一种叫最大子矩阵问题,刚好碰到了这一题,于是学习分享之。
让我们先来看一下题目:ZOJ Problem Set - 1074
题目分类:动态规划
题目大意:就是输入一个N*N的矩阵,找出在矩阵中,所有元素加起来之和最大的子矩阵。
例如在 这样一个4*4的矩阵中,元素之和最大的子矩阵为 ,它们之和为15。
这是一个最大子矩阵问题,我们怎么来解决这个问题呢?任何问题都会有它的简化的问题,这是二维的数组,与之对应的,我们可以先尝试一下一维数组。
如果有一个一维数组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。为找到一维数组的最大子数组,我们可以有以下方法。
- 穷举法
for (i = 0; i < n; i++)
{
for (j = 0; j <= i; j++)
{
sum = 0;
for (k = j; k <= i; k++)
sum += a[k];
if (sum > max) max = sum;
}
}
这种方法在n很大情况下,需要运行的次数非常多,有三层循环,所有在n很大的情况下不适用
- 带记忆的递推法
record[0] = 0;
for (i = 1; i <= n; i++) //用下标1~n来储存n个数
record[i] = record[i - 1] + a[i]; //用record记录a[i]前i个的和
max = 0;
for (i = 1; i <= n; i++)
{
for (j = 0; j < i; j++)
{
sum = record[i] - record[j];
if (sum > max) max = sum;
}
}
这种方法的时间复杂度为iO(n2)
- 动态规划
我们来分析一下最优子结构,若想找到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 = 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;
}
说了这么多,这跟最大子矩阵有什么关系呢?当然有关系学啦!二维就是一维的扩展,把二维压扁不就变成一维了吗?
我们假设所求N*N的矩阵的最大子矩阵是从i列到j列,q行到p行,如下图所示(假设下标从1开始)
最大子矩阵就是图示红色部分,如果把最大子矩阵同列的加起来,我们可以得到一个一维数组{a[q][i]+······+a[p][i] , ······ ,a[q][j]+······+a[p][j]} ,现在我们可以看出,这其实就是一个一维数组的最大子段问题。如果把二维数组看成是纵向的一维数组和横向的一维数组,那问题不就迎刃而解了吗?把二维转换成了我们刚刚解决了的问题。
#include <iostream>
#include <cstring>
using namespace std;
int maxsub(int a[], int n)
{
int i, max = a[0], b = 0;
for (i = 0; i < n; i++)
{
if (b + a[i] >= a[i]) //当n-1的最大字段和+a[i]>=原来的最大字段和
b += a[i]; //则n的最大字段和为b+a[i]
else //否则,就是a[i]
b = a[i];
if (b > max) //找到最大的最大字段和
max = b;
}
return max;
}
int main()
{
int n, i, j, k, maxsubrec, maxsubarr, m;
int dp[101][101], arr[101];
cin >> n >> m;
for (i = 0; i < n; i++)
for (j = 0; j < m; j++)
cin >> dp[i][j];
maxsubrec = dp[0][0];
for (i = 0; i < n; i++) //i用于标识从哪行开始,依次选择行
{
memset(arr, 0, sizeof(arr));
for (j = i; j < n; j++) //从第i行开始,j为选择几行
{
for (k = 0; k < m; k++) //从1列选择到m列,压缩行
arr[k] += dp[j][k];
maxsubarr = maxsub(arr, m); //从每一个压缩行中选择最大字段和
if (maxsubarr > maxsubrec) maxsubrec = maxsubarr;
}
}
cout << maxsubrec << endl;
return 0;
}