最大子段和之M子段和

Posted hbhszxyb

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了最大子段和之M子段和相关的知识,希望对你有一定的参考价值。

最大M子段和

题目模型

  • N个整数组成的序列 (a_1,a_2,a_3,…,a_n) ,将这N个数划分为互不相交的M个子段,并且这M个子段的和是最大的。

问题分析

  • 方法一

    • 看到序列,我们首先要尝试用线性dp去处理,线性dp经典状态定义:f[i][j]i一般表示序列的前i个元素,j表示限制,这里表示划分了j个不相交的子段,我们还需要对i进行进一步的定义,即是否包含第i项,因为对当前元素a[i]来说,要么单独成一个子段,要么和最后一个子段合并,所以必须包含第i个元素。

    • 动态转移方程:dp[i][j]=max(dp[i-1][j],dp[k][j-1])+a[i] (j-1<=k<i)

    • Code

      #include <bits/stdc++.h>
      const int maxn = 1e3+3,Inf=0x3f3f3f3f;
      typedef long long LL;
      int a[maxn],dp[maxn][maxn];
      void Solve(){
          int n,m;scanf("%d%d",&n,&m);
          for(int i=1;i<=n;++i)
              scanf("%d",&a[i]);
          for(int i=1;i<=n;++i){//前i个元素
              for(int j=1;j<=std::min(i,m);++j){//划分出j个子段
                  if(i==j)dp[i][j]=dp[i-1][j-1]+a[i];//显然
                  else{
                      int temp=dp[i-1][j];//把a[i]直接并到最后一子段
                      for(int k=j-1;k<i;++k)//枚举上一个状态的最后一个子段的右端点,a[i]单独作为一个子段
                          temp=std::max(temp,dp[k][j-1]);
                      dp[i][j]=temp+a[i];
                  }            
              }
          }    
          int ans=-Inf;
          for(int i=m;i<=n;++i)
              ans=std::max(ans,dp[i][m]);
          printf("%d
      ",ans);
      }
      int main(){
          Solve();
          return 0;
      }
      
    • 时间效率为:(O(n^3)) ,空间效率为:(O(m*n))

  • 方法二

    • 我们尝试对方法一的dp阶段和状态进行修改, 即把子段限制数M作为阶段,即状态dp[i][j]表示把序列前j分成i个子段且包含a[j]的最大子段和。

    • 动态转移方程有:dp[i][j]=max(dp[i][j-1],dp[i-1][k])+a[j] (i-1<=k<j)

      • dp[i][j-1]+a[i]:表示合并到最后一个子段里

      • dp[i-1][k]+a[i]:表示前k元素挑出k个子段,所以k>=j-1,然后a[i]单独的子段。

      • 此动态转移方程同样满足无后效性和最优子结构。

      • 我们把问题的所有状态记录下来形成一个二维矩阵,显然当前状态只跟它上一行和左边的状态有关,我们可以把空间效率压掉以为变成 (O(n))

      • 同时上一行的状态只有在当前状态前面的最大值对转移有用,我们可以在遍历当前行时维护一下上一行的最大值,这样时间效率就压掉了一个n,变成(O(n*m))

      • Code

        #include <bits/stdc++.h>
        typedef long long LL;
        const int maxn = 1e4+5;
        const LL Inf=0x3f3f3f3f3f3f3f3f;
        LL a[maxn],dp[2][maxn];
        void Solve(){
            int n,m;scanf("%d%d",&n,&m);
            for(int i=1;i<=n;++i)
                scanf("%lld",&a[i]);
            int k=1;//滚动数组指针,k表示当前行,!k表示上一行
            for(int i=1;i<=m;++i,k=!k){//枚举区间个数
            	LL Max=-Inf;
            	for(int j=i;j<=n;j++){
            		Max=std::max(Max,dp[!k][j-1]);//记录前j-1,分成i-1个区间时最大值
            		if(i==j)
            			dp[k][j]=dp[!k][j-1]+a[j];
            		else//要么是a[j]单独成一个区间,此时为Max+a[j],或者直接合并为dp[k][j-1]+a[j]
            			dp[k][j]=std::max(Max,dp[k][j-1])+a[j]; 		
            	}
            }
            
            LL ans=-Inf;
            for(int i=m;i<=n;++i)//!k行才记录的是第m行的状态
            	ans=std::max(ans,dp[!k][i]);
            printf("%lld
        ",ans);
        }
        int main(){
            Solve();
            return 0;
        }
        

以上是关于最大子段和之M子段和的主要内容,如果未能解决你的问题,请参考以下文章

最大子段和之可交换

最大子段和之带长度限制

顺序表应用8:最大子段和之动态规划法

顺序表应用8:最大子段和之动态规划法

最大子段和问题,最大子矩阵和问题,最大m子段和问题

51nod 1052 最大M子段和 & 1053 最大M子段和 V2