POJ1050 DP

To the Max

Description

Given atwo-dimensional array of positive and negative integers, a sub-rectangle is anycontiguous sub-array of size 1*1 or greater located within the whole array. Thesum of a rectangle is the sum of all the elements in that rectangle. In thisproblem the sub-rectangle with the largest sum is referred to as the maximalsub-rectangle. 
As an example, the maximal sub-rectangle of the array: 

0 -2 -7 0 
9 2 -6 2 
-4 1 -4 1 
-1 8 0 -2 
is in the lower left corner: 

9 2 
-4 1 
-1 8 
and has a sum of 15. 

Input

The input consistsof an N * N array of integers. The input begins with a single positive integerN on a line by itself, indicating the size of the square two-dimensional array.This is followed by N^2 integers separated by whitespace (spaces and newlines).These are the N^2 integers of the array, presented in row-major order. That is,all numbers in the first row, left to right, then all numbers in the secondrow, left to right, etc. N may be as large as 100. The numbers in the arraywill be in the range [-127,127].

Output

Output the sum ofthe maximal sub-rectangle.

Sample Input

4

0 -2 -7 0 9 2 -6 2

-4 1 -4 1 -1

 

8  0-2

Sample Output

15

 

 

 

 

分析题目后易知需用到动态规划。

         最初的思路是从第一个元素开始,逐个开始查找最大的。但是子矩阵(sub-rectangle)可能出现在任何一个起始地址和结束地址。如果用这样的遍历,将会使得程序的时间复杂度非常大。

       经过片刻思考,心生一计,计算全部的数据,然后开始逐个减少行或者列,找到最大的子矩阵(the maximalsub-rectangle)。这种不是从最小的地方开始遍历比较查找,而是从最大的数组,即全部数组,逐个减少,达到了可以从任何方向减少元素,这样达到了子矩阵位置的任意性的选择。

         接下来考虑到实现问题,动态生成二维数组的方法,除了指针就是向量,最后决定用向量。整个数据便是vector<vector<int>>类型的一个二维向量。

         在逻辑部分,设计成坐标形势记录子矩阵的起始位置和结束位置。用startX,startY,endX,endY表示。动态规划中的递归地定义最优值这一步便可以作如下处理:对于矩阵有4种方法使之减少行或列,加上不减少任何行和列,一共有五种情况。

/*

         fivecase: start * end   (start+row) *end    (start+column) * end    start * (end+row)    start * (end+column)

*/

那么暂时的结果便是以上5个中的一个。然后把暂时的结果当成当前的父矩阵,继续求解其子矩阵。再从5个结果中选出最大的!

         这便是我的最初设计和实现步骤。实现如代码1所示。

……

         但是经过运行发现结果是错误的!

         稍加思考便发现了程序里面最傻B的错误了。

0 -2  -7  0 
9  2  -6  2
-4  1  -4  1 
-1  8   0 -2

 

例如以上的4*4矩阵,在经过我的程序运行后,得出的最大子矩阵是:

9  2  -6  2
-4  1  -4  1 
-1  8   0 -2
因为:
 
 
9  2  -6 
-4  1  -4 
-1  8   0 

 

 
9  2  -6  2  
-4  1  -4  1
-1  8   0 -2

 

 
 

                       >            

 
 
但是:
 
 
9  2 
-4  1 
-1  8 

 

 
9  2  -6  2  
-4  1  -4  1
-1  8   0 -2

 

 
 

                      >

 

 

这说明,每次只减少一行或者一列是一个极其弱智的错误!

以上最初设计程序时最欠考虑的办法。

现在只有采取补救措施了。便给每次的计算过程一个degree变量,让每次减少degree(degree = 1,2,……N)。但是这样做不到减少1行再减少3列再减少2行这样灵活的操作。而且,这样貌似也没有怎么用到动态规划的精华了。

如果是每次减少一行或者一列的话都考虑减少的数量是1到N的话,那样时间复杂度将会更大!

最初的设计,至此告终!

 

 

 

 

 

 

 

         在不断地纠正中,往往就能发现新的方法。

         头脑中展现的一个新的思路是:使用贪心的思想,如果最边上的a(a=1,2,……n)行或a列,其行内或列内的元素和是一个负数,并且这一行或者列的负数的绝对值大于其他的a行或者列的负数的和的绝对值的话,那么这一行必定要去掉!直到找不到这样的最外面的a行或者a列。这便是贪心算法的思想。诚然,贪心算法的最重要一点便是证明贪心的正确性。关于证明,显而易见,如果最外表的a行是绝对值最大的负数的话,那么去掉后的子矩阵值肯定会变大,证毕。

         那么接下来便是贪心的实现了。实现见代码2。

 

 

 

 

 

 

 

针对代码2,即减掉最边上的a行或列这种思路,可举一反例如下:

 

8   0  -7  -8
1   1   1   2
1   1  -4   1
1   1   1   2

 

 
 

 


                     

 

 

 

按照代码2的思路,会先减掉第一行,然后检查终止条件

最终得到的最大子矩阵是:

 

1   1   1   2
1   1  -4   1
1   1   1   2

 

 
                        

  

 

Actually,最大的子矩阵应该是:

8   0 
1   1 
1   1 
1   1 

 

 
 

 

 

 

 

 

 


理论上第二种思路错误,通过反例也可以看出,这种思路是错误的!

 

 

但这并不说明用动态规划的思路是错误的!

只说明,动态规划的第一步“分析问题的最优解,找出最优解的性质,并刻画其结构特征”做错了。

 

 

最后经过研究诸多网友的解法,方才领悟本题的考点所在(由于本人初学,自认为的考点~哈哈~)。部分内容转自网友:

 

1、首先考虑一维的最大子段和问题,给出一个序列a[0],a[1],a[2]...a[n],求出连续的一段,使其总和最大。

a[i]表示第i个元素
dp[i]表示以a[i]结尾的最大子段和

dp[i] = max{a[i],dp[i-1] + a[i]}

解释一下方程:

如果dp[i-1] > 0,则 dp[i] = dp[i-1] +a[i]
如果dp[i-1] < 0,则 dp[i] = a[i]

因为不用记录位置信息,所以dp[]可以用一个变量dp代替:

如果dp > 0,则dp += a[i]
如果dp < 0,则dp = a[i]

2、考虑二维的最大子矩阵问题

我们可以利用矩阵压缩把二维的问题转化为一维的最大子段和问题。因为是矩阵和,所以我们可以把这个矩形的高压缩成1,用加法就行了。

恩,其实这个需要自己画图理解,我的注释里写得很详细了,自己看吧。
    

#include<iostream>

using namespace std;

#include<vector>

 

 

int temp[101];

int map[101][101];

// 求最大字段的和

int max(int *arr, int n){

    intmax = 0, dp = 0;

   for(int i=0; i < n; i++)

       if(dp < 0)

           dp= arr[i];

       else {

           dp += arr[i];

           if(dp > max)

              max=dp;

        }

    returnmax;

}

 

int main(){

    int N;

    cin>>N;

    int i,j, k;

    for(i= 0; i < N; i++)

       for(j = 0; j < N; j++)

           cin>>map[i][j];

 

    int maxAll=0,maxOne=0;

    //一二层的两个循环用来依次将一个二维图压缩成一维数列

从第一行到最后一行(for),当前为第i行

         从第i行到最后一行(for),当前为第j行

[以上两个循环就涵盖了所以连续相邻的行数的所有组合]

                   统计每一列的m行元素和(for)。m即为相邻的行的个数,这样便压缩了矩阵的所有子矩阵为一维数组。

                   然后对压缩后的一维数组求连续元素的和的最大值。再保存最大值并且不断更新。

 

重复以上步骤,遍历所有连续行的组合并统计当前列的最新值,更新最大值。最后即为结果。

 
 

 


    for(i = 0; i < N; i++)

    {   

       //自上而下

       memset(temp, 0, sizeof(temp));

       for(j = i; j < N; j++)

       {

           //自上而下

           for(k = 0; k < N; k++)

             temp[k] += map[j][k];//自左到右

          

           maxOne= max(temp, N);

           if(maxOne> maxAll)

              maxAll= maxOne;

       }

    }

 

   cout<<maxAll<<endl;

    return0;

 

}

        

 

 

对于程序的画图解释(以4*4的矩阵):

对三层循环的解释

 
 

 

 


temp[0]  temp[1] temp[2]  temp[3]

 8        0       -7        -8

1        1         1         2
1        1        -4         1
1        1         1         2
 

temp[0]  temp[1] temp[2]  temp[3]

8        0        -7        -8
1        1         1         2
1        1        -4         1
1        1         1         2

 

 

 

temp[0]  temp[1] temp[2]  temp[3]

8        0        -7        -8
1        1         1         2
1        1        -4         1
1        1         1         2

 

 

 

 

temp[0]  temp[1] temp[2]  temp[3]

8        0        -7        -8
1        1         1         2
1        1        -4         1
1        1         1         2

 

 

 

 

temp[0]  temp[1] temp[2]  temp[3]

8        0        -7        -8
1        1         1         2
1        1        -4         1
1        1         1         2

 

 

 

 

 

temp[0]  temp[1] temp[2]  temp[3]

8        0        -7        -8
1        1         1         2
1        1        -4         1
1        1         1         2

 

 

 

temp[0]  temp[1] temp[2]  temp[3]

8        0        -7        -8
1        1         1         2
1        1        -4         1
1        1         1         2

 

 

 

temp[0]  temp[1] temp[2]  temp[3]

8        0        -7        -8
1        1         1         2
1        1        -4         1
1        1         1         2

 

 

temp[0]  temp[1] temp[2]  temp[3]

8        0        -7        -8
1        1         1         2
1        1        -4         1
1        1         1         2

 

temp[0]  temp[1] temp[2]  temp[3]

8        0        -7        -8
1        1         1         2
1        1        -4         1
1        1         1         2

 

 

 

 

 

 

 

 

 

代码1:

#include<iostream>

#include<vector>

using namespace std;

int N = 4;

vector< vector<int> > rec;

int degree = 0;

 

 

 

int countVector(vector<vector<int> > recParent, int startX, int startY, int endX, int endY)

{

         intsum = 0;

         for(int i = startX; i < endX; i++)

         {

                   for(int j = startY; j < endY; j++)

                   {

                            //cout<<recParent[i][j]<<"  ";

                            sum+= recParent[i][j];

                   }

                   //cout<<endl;

         }

 

         returnsum;

 

}

 

 

int fun(int startX, int startY, int endX,int endY)

{

         /*

         fivecase: start * end   (start+row) *end    (start+column) * end    start * (end+row)    start * (end+column)

         */

         intmax0 = 0;   //start * end

         intmax1 = 0;    //(start+row) * end

         intmax2 = 0;    //(start+column) * end

         intmax3 = 0;    //start * (end-row)

         intmax4 = 0;    //start * (end-column)

         intresult = 0;

 

         max0= countVector(rec, startX, startY, endX, endY);

         if((startX+degree) < N)

         {

                   max1= countVector(rec, startX+degree, startY, endX, endY);

         }

         if((startY+degree) < N)

         {

                   max2= countVector(rec, startX, startY+degree, endX, endY);

         }

         if((endX-degree) > -1)

         {

                            max3= countVector(rec, startX, startY, endX-degree, endY);

         }

         if((endY-degree) > -1)

         {

                   max4= countVector(rec, startX, startY, endX, endY-degree);

         }

                  

 

         if(max0 >= max1 && max0 >= max2 && max0 >= max3&& max0 >= max4)

         {

                   result=  max0;

         }

         elseif (max1 >= max0 && max1 >= max2 && max1 >= max3&& max1 >= max4)

         {

                   result= fun(startX+degree, startY, endX, endY);

         }

         elseif (max2 >= max0 && max2 >= max1 && max2 >= max3&& max2 >= max4)

         {

                   result= fun(startX, startY+degree, endX, endY);

         }

         elseif (max3 >= max0 && max3 >= max1 && max3 >= max2&& max0 >= max4)

         {

                   result= fun(startX, startY, endX-degree, endY);

         }

         elseif (max4 >= max0 && max4 >= max1 && max0 >= max2&& max0 >= max3)

         {

                   result= fun(startX, startY, endX, endY-degree);

         }

 

         returnresult;

}

 

 

 

void main()

{

         vector<int>v;

        

         v.push_back(0);

         v.push_back(-2);

         v.push_back(-7);

         v.push_back(0);

         rec.push_back(v);

 

         v.clear();/

         v.push_back(9);

         v.push_back(2);

         v.push_back(-6);

         v.push_back(2);

         rec.push_back(v);

        

         v.clear();/

         v.push_back(-4);

         v.push_back(1);

         v.push_back(-4);

         v.push_back(1);

         rec.push_back(v);

        

         v.clear();/

         v.push_back(-1);

         v.push_back(8);

         v.push_back(0);

         v.push_back(-2);

         rec.push_back(v);

        

         v.clear();/

 

         //countthe sum of the maximal sub-rectangle.

         /*

         fivecase: start * end   (start+row) *end    (start+column) * end    start * (end+row)    start * (end+column)

         */

//     intstartX = 0;

//     intstartY = 0;

//     intendX = N;

//     intendY = N;

         intres = 0;

        

         while(degree < N)

         {

                   inttemp = fun(0,0,N,N);

                   res= temp>res?temp:res;

                   degree++;

         }

 

        

         cout<<res<<endl;

        

 

 

 

 

//     for(int i=0; i<rec.size(); i++)

//     {

//              for(int j=0;j<rec[i].size();j++)

//              {

//                       cout<<rec[i][j]<<"  ";

//              }

//              cout<<endl;

//     }

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

代码2:

#include <iostream>  
#include <vector>  
using namespace std;
 
int A[10000];
int N;  
vector< vector<int> > rec;  
int degree = 1;       //减掉的程度,即一次性减掉几行/列
 
void input()
{     
      cin >> N; 
      while(!cin) { 
        cout << "error. " <<endl; 
        cout << "reinput number: "; 
        cin.clear();
            cin.sync();       
        cin >> N;
      }
 
      int arrayLength = N*N;
 
      for (int k = 0; k < arrayLength; k++)
      {
            cin>>A[k];
      }
 
      int index = 0;
      while (index < arrayLength)
      {
            vector<int> v;
            for (int x = 0; x < N; x++) 
                  v.push_back(A[index++]);
            rec.push_back(v);
            v.clear();/
      }
 
//  for (int i=0; i < rec.size(); i++)
//    {
//          for (int j=0;j < rec[i].size();j++)
//     {
//                       cout<<rec[i][j]<<"  ";
//          }
//          cout<<endl;
//    } 
 
 
}
 
 
int countVector(int startX, int startY, int endX, int endY) 
{ 
      int sum = 0;
      for (int i = startX; i <= endX; i++)
      {
            for (int j = startY; j <= endY; j++)
            {
                  //cout<<recParent[i][j]<<"  ";
                  sum += rec[i][j];
            }
            //cout<<endl;
      }
 
      return sum;
 
}
 
//返回degree行的元素总和
int countStartRow(int row, int begin, int end) 
{
      int rlSum = 0;
      int degreeTemp = degree;
      while (degreeTemp > 0 && (row + degreeTemp - 1) < N )
      {
            for (int x = begin; x <= end; x++)
            {
                  rlSum += rec[row + degreeTemp - 1][x];
            }
            degreeTemp--;
      }
      return rlSum;
}
 
//返回degree行的元素总和
int countEndRow(int row, int begin, int end) 
{
      int rlSum = 0;
      int degreeTemp = degree;
      while (degreeTemp > 0 && (row - degreeTemp + 1) > -1 )
      {
            for (int x = begin; x <= end; x++)
            {
                  rlSum += rec[row - degreeTemp + 1][x];
            }
            degreeTemp--;
      }
      return rlSum;
}
 
//返回degree列的元素总和
int countStartColumn(int column, int begin, int end) 
{
      int rlSum = 0;
      int degreeTemp = degree;
      while (degreeTemp > 0 && (column + degreeTemp - 1) < N)
      {
            for (int x = begin; x <= end; x++)
            {
                  rlSum += rec[x][column + degreeTemp - 1];
            }
            degreeTemp--;
      }
      return rlSum;
}
 
//返回degree列的元素总和
int countEndColumn(int column, int begin, int end) 
{
      int rlSum = 0;
      int degreeTemp = degree;
      while (degreeTemp > 0 && (column - degreeTemp + 1) > -1)
      {
            for (int x = begin; x <= end; x++)
            {
                  rlSum += rec[x][column - degreeTemp + 1];
            }
            degreeTemp--;
      }
      return rlSum;
}
 
 
int fun(int startX, int startY, int endX, int endY) 
{
      int result = 0;       //返回的结果
 
 
      int startRow = 0;     //起始行的元素总和
      int startColumn = 0;  //起始列的元素总和
      int endRow = 0;       //结束行的元素总和
      int endColumn = 0;    //结束列的元素总和
 
 
      while (degree < N)
      {
            startRow = countStartRow(startX, startY, endY);
            endRow = countEndRow(endX, startY, endY);
            startColumn = countStartColumn(startY, startX, endX);
            endColumn = countEndColumn(endY, startX, endX);
      
            if (startRow >= 0 && startColumn >= 0 && endRow >= 0 && endColumn >= 0) //达到终止条件
            {
                  //result = countVector(startX, startY, endX, endY);
                  if ( (endX - startX) > degree || (endY - startY) > degree)
                        degree++;
                  else
                        break;
            }
            else if (startRow < startColumn && startRow < endRow && startRow < endColumn)//减掉起始行
            {
                  startX = (startX + degree) < N ? (startX + degree) : startX;
                  degree = 1; 
                  continue;
            }
            else if (startColumn < startRow && startColumn < endRow && startColumn < endColumn)//减掉起始列
            {
                  startY = (startY + degree) < N ? (startY + degree) : startY;
                  degree = 1; 
                  continue;
            }
            else if (endRow < startRow && endRow < startColumn && endRow < endColumn)//减掉结束行
            {
                  endX = (endX - degree) > 0 ? (endX - degree) : endX;
                  degree = 1; 
                  continue;
            }
            else if (endColumn < startRow && endColumn < startColumn && endColumn < endRow)//减掉结束列
            {
                  endY = (endY - degree) > 0 ? (endY - degree) : endY;
                  degree = 1; 
                  continue;
            }
            else
            {
                  result = -10000;
            }
      }
 
      result = countVector(startX, startY, endX, endY);
 
      return result;
}
 
 
 
void main()
{     
      input();
      int res = fun(0,0,N-1,N-1);    
      cout<<res<<endl;   
 
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值