码迷,mamicode.com
首页 > 其他好文 > 详细

最大子段和之M子段和

时间:2020-06-15 15:48:08      阅读:86      评论:0      收藏:0      [点我收藏+]

标签:时间   amp   ++   修改   状态   最大值   void   class   bit   

最大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\n",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\n",ans);
        }
        int main(){
            Solve();
            return 0;
        }
        

最大子段和之M子段和

标签:时间   amp   ++   修改   状态   最大值   void   class   bit   

原文地址:https://www.cnblogs.com/hbhszxyb/p/13130933.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!