zoukankan      html  css  js  c++  java
  • 51nod 1051 最大子矩阵和 【最大子段和DP变形/降维】

     【题目】:

    一个M*N的矩阵,找到此矩阵的一个子矩阵,并且这个子矩阵的元素的和是最大的,输出这个最大的值。
    例如:3*3的矩阵:
    
    -1 3 -1
    2 -1 3
    -3 1 2
    
    和最大的子矩阵是:
    
    3 -1
    -1 3
    1 2
    Input
    第1行:M和N,中间用空格隔开(2 <= M,N <= 500)。
    第2 - N + 1行:矩阵中的元素,每行M个数,中间用空格隔开。(-10^9 <= M[i] <= 10^9)
    Output
    输出和的最大值。如果所有数都是负数,就输出0。
    Input示例
    3 3
    -1 3 -1
    2 -1 3
    -3 1 2
    Output示例
    7
    View Code

    【分析】:

    在做这道题之前必须会最大子段和这一道题的基础,这也是动态规划中最简单的一道例题,而这道题是最大字段和的一个扩展。

    例子: 0,2,7,2,-5最大的一个连续子序列之和。

    分析:这五个数有这样几种和的组合:0;02;027;0272;……这样5!个数。

       用数组索引来表示就是:0;0到1;0到2;0到3……1;1到2;……(这里是数组索引表示)

       我们显然不能用多重循环把所有数都算出来,我们换一种角度思考,如果只有一个数字,那么最大的连续子序列数多少,还是以上面的例子为例,应该是0吧,那么现在有两个数字呢?有两种情况,一种是前面有一个数字的最大值(已求)+现在这个数字;另外一种情况是就是只有这个数字;然后算出来的最大值与只有一个数字的情况的最大值相比较;现在有三个数字,也有两种情况,一种是前面有前面两个数字的最大值+现在这个数字;或者就是只是这个数字;……

    第一行:N代表这个N*N矩阵的维数
    
    int max_sum(int n)
    {
             int i, sum = 0, max = INT_MIN;  
             for(i = 0; i < n; i++)
             {  -------------------------------------------------------------------------------0
                    if(sum < 0)
                            sum = 0;
                    sum += a[i];      ---------------------------------------------------------1
                    if(sum > max)
                            max = sum;---------------------------------------------------------2
             }
             return max;
    }
    

      

    ----0----代表蓝色部分【这个不用说循环】

    ----1----代表橙色部分【**下面说**】

    ----2----代表紫色部分

    我们橙色的字;因为一共有两种情况,一种是自己本身,一种是前i-1项的最大值,我们换个思路想一下,假如第1个数(索引为0的数)为负数,那么下面前两个数的最大最一定是第二个数其本身,即第二次是把sum=0;sum+=a[1]  然后记录max值。。。实际上就是假如你前n个数是正的那么可以继续加上去,如果你前n个数是负的那么把值记录一下就好了。所以只要求前n项和和最大值就好了。

    当然也有人是这样写的:

    int MaxSum(int n,int *a)  
    {  
        int sum=0,b=0;  
        for(int i=1; i<=n; i++)  
        {  
            if(b>0)  
            {  
                b+=a[i];  
            }  
            else  
            {  
                b=a[i];  
            }  
            if(b>sum)  
            {  
                sum = b;  
            }  
        }  
        return sum;  
    } 
    

      

    或者更加直白的话是这样写的,这样更加容易理解一些,看个人的编程习惯了:

    int max_sum2(int n)
    {
         b[0]=a[0];    //数组b存放前n项的最大值
         int maximal=b[0];
         for(int i=1;i<n;i++)
         {
               b[i]=max(b[i-1]+a[i],a[i]); //STL
               if(b[i]>maximal)
                    maximal=b[i];
         }
         return maximal;
    }
    

      下面写二维的矩阵这道题,我们首先要做的就是降维然后像上面一样做。

    先写一下思路,为什么说是最大字段和的扩展。
    我们原来是一行,如果算到两行的。。。现在我们下面一行跟着上面一行一起动,再求和。即如果这个矩阵只有两行,我们先求第一行的最大值(最大字段和),然后让第二行跟着第一行一起动【其实就是把两行看成一行】,求出两行的最大值。。。以此类推。
     
    我们知道一维时候的最大字段和,要有sum【前n个数之和】和最大值这两个变量,我们这里的sum怎么求呢? sum[i][j]从表示第i行第j个元素变成表示第i行前j个元素和,这样sum[k][j]-sum[k][i]就可以表示第k行从i->j列的元素和。
     

    科幻小说《三体》中描绘了恢弘壮丽的“降维攻击”的场景: 
    “歌者”随手抛下了一张“二向箔”,整个银河系的三维空间奔腾汹涌地流入二向箔,塌缩成一个二维平面,三维结构被碾压在二维平面之上。同时,这一降维过程是全息的,所有的三维信息被保留在碾压后的二维空间里。这种致命的攻击令攻击者和被攻击者同归于尽,玉石俱焚,其结局黑暗得令人窒息。

    我们知道,在线性空间的最大子段和,已有线性时间的DP算法。有没有可能,把二维的最大矩阵和转化为一维的最大子段和呢?

    答案是肯定的,算法有着似三体描述的未来武器般的威力

    对于一个子矩阵,有四个属性:起始行位置 i , 结束行位置 j, 起始列 x, 结束列 y

    我们可以枚举i, j, 然后对后列进行降维压缩

    再对降维后的一维数组进行最大子序列和动态规划就行了。

    #include<cstdio>
    #include<cstring>
    #include<queue>
    #include<iostream>
    #include<stack>
    #define maxn 505
    #define maxm 50005
    #define INF 0x3f3f3f3f
    #define ll long long
    using namespace std;
    
    int n,m;
    int a;
    int dp[maxn][maxn];
    //int sum[maxn][maxn];
    
    
    int main()
    {
        while(cin>>m>>n)
        {
            memset(dp,0,sizeof(dp));
            for(int i=1;i<=n;i++){
                for(int j=1;j<=m;j++){
                    scanf("%d",&a);
                    dp[i][j]=dp[i-1][j]+a;////让dp存放第i行的前j项和
                //其实我们用数学之美来看为什么要这样存放,你一维的时候存放的是一维的数据
                //二维的时候再存放一维的数据就是错误的,二维的时候就应该存放二维的数据
                //当然你也不要去存放前i行前j列之和,这个相比于前面的就是三维的数据了
                //如果你算的是长方体的数据,这样应该是对的,那么我们的数组也应该是三维的了
                }
            }
            //s数组是用来记录起点行到终点行的每一竖条的数值和的,ans是用来记录不同的子矩阵和的(需要相邻竖条相加),
            int ans=0;
            for(int i=1;i<=n;i++){//这n行数有这样几种和的组合:0;02;027;0272;……这样n!个数。
                //用数组索引来表示就是:0;0到1;0到2;0到3……1;1到2;……(这里是数组索引表示)
                for(int j=i;j<=n;j++){//前面两层循环是列循环,比如三列的话就是,1-2列,1-3列,2-3列
                    int sum=0;
                    for(int k=1;k<=m;k++){
                        sum+=(dp[j][k]-dp[i-1][k]);//对i到j行矩阵进行降维操作
                        if(sum<0) sum=0;
                        if(sum>ans) ans=sum;
                        //ans=max(ans,sum);
                    }
                }
            }
            printf("%d
    ",ans);
        }
        return 0;
    }
    注意行列n,m输入顺序

     

  • 相关阅读:
    io工具类
    并发高级知识
    HashMap相关源码阅读
    ArrayList和LinkedList部分源码分析性能差异
    我自己的JdbcTemplate
    mysql5.7.20靠谱安装步骤
    NG 转发配置
    SQLite总结
    算是不常用的东西,java中的ResultSet转List
    不常用的技能-【手动编译java类】
  • 原文地址:https://www.cnblogs.com/Roni-i/p/8946518.html
Copyright © 2011-2022 走看看