动态规划:最大子矩阵
在DP问题中有一种叫最大子矩阵问题,刚好碰到了这一题,于是学习分享之。
让我们先来看一下题目:ZOJ Problem Set - 1074
http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1074
题目分类:动态规划
题目大意:就是输入一个N*N的矩阵,找出在矩阵中,所有元素加起来之和最大的子矩阵。
例如在 0 -2 -7 0 这样一个4*4的矩阵中,元素之和最大的子矩阵为 9 2 ,它们之和为15。
9 2 -6 2 -4 1
-4 1 -4 1 -1 8
-1 8 0 -2
这是一个最大子矩阵问题,我们怎么来解决这个问题呢?任何问题都会有它的简化的问题,这是二维的数组,与之对应的,我们可以先尝试一下一维数组。
如果有一个一维数组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。为找到一维数组的最大子数组,我们可以有以下方法。
1、穷举法
1 for(i=0;i<n;i++) 2 { 3 for(j=0;j<=i;j++) 4 { 5 sum = 0; 6 for(k=j;k<=i;k++) 7 sum += a[k]; 8 if(sum > max) max = sum; 9 } 10 }
穷举法在n很大的情况下,需要运行的次数非常的多,有三层循环,所以n很大时不能使用这种方法。
2、带记忆的递推法
1 record[0] = 0; 2 for(i=1;i<=n;i++) //用下标1~n来储存n个数 3 record[i] = record[i-1] + a[i]; //用record记录a[i]前i个的和 4 max = 0; 5 for(i=1;i<=n;i++) 6 { 7 for(j=0;j<i;j++) 8 { 9 sum = record[i] - record[j]; 10 if(sum > max) max = sum; 11 } 12 }
这种方法的时间复杂度明显比上一种的低了很多,时间复杂度为O(n²)。这种方法其实我们再继续优化一下,就变成了我们所需要的动态规划。
3、动态规划
我们来分析一下最优子结构,若想找到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}, 算法如下:
1 int MaxSubArray(int a[],int n) 2 { 3 int i,b = 0,sum = 0; 4 for(i = 0;i < n;i++) 5 { 6 if(b>0) // 若a[i]+b[i-1]会减小 7 b += a[i]; // 则以a[i]为首另起一个子段 8 else 9 b = a[i]; 10 if(b > sum) 11 sum = b; 12 } 13 return sum; 14 }
说了这么多,这跟最大子矩阵有什么关系呢?当然有关系学啦!二维就是一维的扩展,把二维压扁不就变成一维了吗?
我们假设所求N*N的矩阵的最大子矩阵是从i列到j列,q行到p行,如下图所示(假设下标从1开始)
a[1][1] a[1][2] ······ a[1][i] ······ a[1][j] ······ a[1][n]
a[2][1] a[2][2] ······ a[2][i] ······ a[2][j] ······ a[2][n]
······
a[q][1] a[q][2] ······ a[q][i] ······ a[q][j] ······ a[q][n]
······
a[p][1] a[p][2] ······ a[p][i] ······ a[p][j] ······ a[p][n]
······
a[n][1] a[n][2] ······ a[n][i] ······ a[n][j] ······ a[n][n]
最大子矩阵就是图示红色部分,如果把最大子矩阵同列的加起来,我们可以得到一个一维数组{a[q][i]+······+a[p][i] , ······ ,a[q][j]+······+a[p][j]} ,现在我们可以看出,这其实就是一个一维数组的最大子段问题。如果把二维数组看成是纵向的一维数组和横向的一维数组,那问题不就迎刃而解了吗?把二维转换成了我们刚刚解决了的问题。
最终我们得到了ZOJ Problem Set - 1074的解法,代码如下:http://paste.ubuntu.com/15272521/
1 #include <iostream> 2 #include <cstring> 3 using namespace std; 4 5 int maxsub(int a[],int n) 6 { 7 int i,max=0,b=0; 8 for(i=0;i<n;i++) 9 { 10 if(b > 0) 11 b += a[i]; 12 else 13 b = a[i]; 14 if(b > max) 15 max = b; 16 } 17 return max; 18 } 19 20 int main() 21 { 22 int n,i,j,k,maxsubrec,maxsubarr; 23 int dp[101][101],arr[101]; 24 while(cin>>n) 25 { 26 for(i=0;i<n;i++) 27 for(j=0;j<n;j++) 28 cin>>dp[i][j]; 29 maxsubrec = 0; 30 for(i=0;i<n;i++) 31 { 32 memset(arr,0,sizeof(arr)); 33 for(j=i;j<n;j++) 34 { 35 for(k=0;k<n;k++) 36 arr[k] += dp[j][k]; 37 maxsubarr = maxsub(arr,n); 38 if(maxsubarr > maxsubrec) maxsubrec = maxsubarr; 39 } 40 } 41 cout<<maxsubrec<<endl; 42 } 43 }