• 算法设计与分析 SCAU17089 最大m子段和


    17089 最大m子段和

    时间限制:1000MS 代码长度限制:10KB
    提交次数:0 通过次数:0

    在这里插入图片描述

    题型: 编程题 语言: G++;GCC;VC;JAVA

    Description

    “最大m子段和”问题:给定由n个整数(可能为负)组成的序列a1、a2、a3、…、an,以及一个正整数m,
    要求确定序列的m个不相交子段,使这m个子段的总和最大!

    m是子段的个数。当m个子段的每个子段和都是负值时,定义m子段和为0。


    输入格式

    第一行:n和m; (n,m<10000)
    第二行:n个元素序列,中间都是空格相连。

    比如:
    6 3
    2 3 -7 6 4 -5


    输出格式

    输出最大m子段和。

    比如:
    15

    这15可由这三个段之和来的:(2 3) -7 (6) (4) -5


    输入样例

    6 3
    2 3 -7 6 4 -5


    输出样例

    15


    解题思路

    1. dp 方程定义

    定义二维数组 dp, dp[ i ][ j ],表示前 j 项所构成 i 子段的最大和,且必须包含着第 j 项,即以第 j 项结尾


    2. 状态转移方程

    求 dp[ i ][ j ],有两种情况

    1. dp[ i ][ j ] = dp[ i ] [ j-1 ] + a[ j ] ,即把第 j 项融合到第 j-1 项的子段中,子段数没变(即跟前面连成一段,一般发生于前面的数是正数时);
    2. dp[ i ][ j ] = dp[ i-1 ] [ k ] + a[ j ],(i-1<= k < j ),把第 j 项作为单独的一个子段,然后找一下 i - 1 个子段时,最大的和,然后加上a[ j ]

    对于第二点,思考方式可以是如下图:当你遍历到6这个元素时,很明显不能选择前面的元素(因为是负的),那么就选择其他(尽量正数),于是目光自然就看到了前面那一份 2 + 3 = 5。
    而 k 的取值范围又如何解释呢:

    1. 大于等于 i - 1 ,是因为 i - 1 个数字最多只能被分为 i - 1 份,即每份一个数字;
    2. 而小于 j 即最多可以选择到第 j - 1 个数字,是因为当你在抉择第 j 个数字如何划分范围时,前面只有 j - 1 个数字给你划分,范围怎么可能能跑到 j 自己及后面去。

    在这里插入图片描述
    然后比较上面两种情况,取大的即可。

    下面看图,红色数字为输入的序列:

    动态规划,借助矩阵可以直观的看到计算过程。
    如图,要求dp[ 3 ][ 6 ],只需比较 他左边的那个,和上面那一行圈起来的之中 最大的数(i-1 <= t <= j),
    再加上 a[ j ] 即为 dp[ 3 ][ 6 ] 的值。


    3. 算法解题思路
    1. 初始化 dp 数组,前 0 个数字划分为任意份肯定都为0:dp[i][0] = 0; 任意个数字划分为0份肯定也为0;n 个数字肯定无法划分为 n - 1 份,最多 n 份即每份一个数字:dp[i][i - 1] = -INF(赋值为最小数即可)。
    2. 外面双层循环,第一层 i 是记录划分到第几份,第二层 j 是记录到是把前 j 个数字划分为 i 份。
    3. 在每次划分时,都要去上一份那里去寻找最大值,看看上一份中使用哪几个数字能划分成最大值 maxNum,再跟当前份数的前一个数字来比较:dp[i][j - 1],看看第 j 个数到底是继续划分到前一个数字后面,还是说单独开一份。
    4. 那么 dp[i][j] 就等于第三个步骤中大的那个,再加上当前数字的大小,即为最大和。
    5. 最后,也就是最关键的地方,输出的找法,不是单纯的 dp[m][n],因为最大m字段和不一定包括全部数,可以舍弃掉一些数比如负数,见如下:

    在划分为 m 份时进行寻找,看看使用前几个数字可以找到最大字段和,比如说 5 6 7 -1 2 -8 -9,最大字段和肯定不可能把最后两位划分进去,所以代码应为如下

        // 找划分成 m 份时,究竟前几个数字才能凑成最大字段和
        int res = -INF;
        // 由于是划分为 m 份,所以数字至少得 m 个才能,总不可能2个数字划分成3份吧
        for(i = m; i <= n; i++) {
            res = max(res, dp[m][i]); 
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    4. 可优化点
    1. 沿着第 m 行的最后一个元素,往左上方向画一条线,线右上方的元素是没必要计算的
      那么 dp[ i ][ j ] ,j++ 的时候,j 的上限为 i + n - m 即可。
      还有左下角那一半矩阵,也是不用计算的,因为1个数字不可能分为2个子段
    2. 每确定一个 dp[ i ][ j ],只需用到 本行和上一行,所以不用开维数组也可以,省内存。
      开两个一维数组,pre 和 dp,pre 记录上一行,dp 记录当前行
    3. 再对上一行红圈中的数字找最大值时,若用一个循环来找,容易超时。所以优化方法是:在每次计算 dp 之前,同时记录下j前面的最大元素。

    时间复杂度大致为O(m*(n-m+1)),mn-m方

    通过图片,分析情况1和2,就能发现,从左上角走到第 m 行的最后一个元素即可,找出第 m 行的最大值即为答案。



    更多注释可查看下方的完整代码中,有助于理解

    代码如下
    #include 
    #include 
    #include 
    #include 
    /*
    6 3
    2 3 -7 6 4 -5
    
    7 7
    -2 11 -4 13 -5 6 -2
    */
    
    using namespace std;
    
    const int INF=0x3f3f3f3f;
    
    int a[10001];
    int dp[10001][10001]; // dp[i][j],表示前 j 项所构成 i 子段的最大和,且必须包含着第j项,即以第j项结尾
    
    int main()
    {
        // 此题为求最大上升子序列的变种,是求多个
        int i, j, k, n, m;
        cin >> n >> m;
    
        for(i = 1; i <= n; i++) {
            cin >> a[i];
        }
    
        for(i = 1; i <= n; i++) {
            dp[i][0] = 0;
            dp[0][i] = 0;
            dp[i][i - 1] = -INF; // 每一行开头,即 n 个数字想分成 n - 1 份是不可能,赋值为最小数即可
        }
    
        // 分成 i 段时
        for(i = 1; i <= m; i++) {
            // 前 j 个数
            for(j = i; j <= n; j++) {
                int maxNum = -INF;
                // 去上一行找最大值,索引结束于 j - 1 之前
                for(k = i - 1; k < j; k++) {
                    if(dp[i - 1][k] > maxNum) {
                        maxNum = dp[i - 1][k];
                    }
                }
                dp[i][j] = max(dp[i][j - 1], maxNum) + a[j];
            }
        }
    
        // 找划分成 m 份时,究竟几个数字才是最好的
        int res = -INF;
        // 由于是划分为 m 份,所以数字至少得 m 个才能,总不可能2个数字划分成3份吧
        for(i = m; i <= n; i++) {
            res = max(res, dp[m][i]);
        }
    
        cout << res << endl;
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    优化后代码
    #include
    using namespace std;  
    typedef long long ll;
    const int N=1e6+5; 
    const int INF=0x3f3f3f3f;
    int n,m;
    ll a[N],dp[2][N];   //只保存上一行和当前行 
    int main()  
    {
        while(~scanf("%d%d",&n,&m))   //n个数字,m子段和 
        { 
            for(int i=1;i<=n;i++) 
    			scanf("%lld",a+i);  
    		for(int i=0;i<=n;i++)
    			dp[0][i]=0,dp[1][i]=0;    //关键!此题答案只允许正值
    		
            for(int i=1,k=1;i<=m;i++,k^=1)    //分为i段,k为两行之间的切换
            {
            	dp[k][i-1]=-INF;   //i==j时,杜绝与前一元素共伍
                ll maxpre=-INF;    //maxpre记录上一行的最大值
                for(int j=i;j<=n-m+i;j++)
                {
                    maxpre=max(maxpre,dp[k^1][j-1]);     //随时更新上一行最大值
                    dp[k][j]=max(dp[k][j-1],maxpre)+a[j]; //*对情况1、2的选择
                }
            }
            ll ans=-INF;
            for(int i=m;i<=n;i++)   //找到第m行的最大值,即为答案
                    ans=max(ans,dp[m&1][i]);
            printf("%lld\n",ans);
        }  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32


    最后

    对我感兴趣的小伙伴可查看以下链接

  • 相关阅读:
    前端面试宝典 - Javascript 基础知识
    配置Jetson扩展头--配置CSI相机
    《ABP Framework 极速开发》教程首发
    接口设计时的一些建议
    VScode 关闭新建文件的语言选择提示后如何再开启
    拨号上网和宽带上网的区别分析
    Pycharm正版2022.2.2 | 官方翻译插件更新tkk失败解决
    IIC学习笔记(参考小梅哥教程)
    Netty源码阅读(1)之——客户端源码梗概
    轻松实现远程访问本地wamp服务器,无公网IP也不怕,「内网穿透」
  • 原文地址:https://blog.csdn.net/weixin_53893220/article/details/128003117