• 动态规划:最大子矩阵


      在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 }
  • 相关阅读:
    命令模式
    js代理模式,处理缓存
    js设计模式之策略模式
    查看并修改签名证书keystore的密码,alias别名等相关参数
    【fiddler】配置代理后个别app连不上网的问题
    使用Fiddler域名过滤、断点、小技巧绕过前端验证
    App上架各大应用市场的地址及操作方法
    获取APK获取APK证书MD5、SHA1、SHA256等秘钥
    Python一切皆对象
    WEB基础之布局与定位
  • 原文地址:https://www.cnblogs.com/GodA/p/5237061.html
Copyright © 2020-2023  润新知