动态规划之最大M子段和问题
自学笔记
•参考资料
•何为最大M子段和?
- 给定由 n 个整数(可能为负)组成的序列 $a_1,a_2,\cdots ,a_n$ 以及一个正整数m
- 求序列的 m 个不相交子段,使这 m 个子段的总和最大
特别注意:
有些题目可能不需要必须找到 m 个子段,例如给出的序列中,正整数个数不足 m 个,那么答案是所有正整数的加和。
此时选择的子段个数是全部的正整数个数,不足 m 个,但符合题意,如这道题【51nod 1052 最大M子段和】
也可能有些题目要求,必须选够 m 个子段,如这道题【HDU 1024】。
•问题的解
动态规划,借助矩阵可以直观的看到计算过程。
定义二维数组 $dp$, $dp[i][j]$,表示前 j 项所构成 i 子段的最大和,且必须包含着第 j 项,即以第 j 项结尾;
求 $dp[i][j]$,有两种情况:
- $dp[i][j]=max(dp[i][j],dp[i][j-1]+a[j])$ ,即把第 j 项融合到第 j-1 项的子段中,子段数没变
- $dp[i][j]=max(dp[i][j],dp[i-1][k]+a[ j ])\ ,\ (i-1\leq k < j)$,即第 j 项单独作为一个子段加入到前 j-1 个形成的 i-1 个子段中;
如图所示,红色数字为输入的序列:
如图,要求 $dp[3][6]$,只需比较他左边的那个,和上面那一行圈起来的之中最大的数+$a_j$ 即可;
优化:
- 沿着第 m 行的最后一个元素,往左上方向画一条线,线右上方的元素是没必要计算的
- 因为线右上方的元素没必要计算,所以,在计算第 $i$ 行的 $dp[i][j]$ 时,$j$ 的范围为 $[i,n-m+i]$
- 左下角那一半矩阵,也是不用计算的,因为分成 $i$ 段至少需要 $i$ 个数字
- 每确定一个 $dp[i][j]$,只需用到本行和上一行,所以不用开二维数组也可以,省内存
•Code
View Code1 #include<bits/stdc++.h> 2 using namespace std; 3 #define ll long long 4 #define INFll 0x3f3f3f3f3f3f3f3f 5 #define mem(a,b) memset(a,b,sizeof(a)) 6 const int maxn=1e6+50; 7 8 int n,m; 9 ll a[maxn]; 10 ll dp[maxn]; 11 ll Max[maxn]; 12 13 ll Solve() 14 { 15 /** 16 二位数组中,dp[i][j]的求解依赖 dp[i][j-1] 以及 max(dp[i-1][j-1],dp[i-1][j-2],...,dp[i-1][i-1]) 17 因为只需要当前行和上一行的最大值 18 所以,我定义 Max[j] 表示前一行中[i-1,j]的dp最大值 19 */ 20 mem(Max,0); 21 for(int i=1;i <= m;++i) 22 { 23 dp[i-1]=-INFll;///i-1个数不能划分成i段,所以将dp[i-1]赋值为-INFll 24 for(int j=i;j <= n-m+i;++j) 25 dp[j]=max(dp[j-1],Max[j-1])+a[j]; 26 27 Max[i-1]=-INFll;///i-1个数不能划分成i段,所以将Max[i-1]赋值为-INFll 28 for(int j=i;j <= n-m+i;++j) 29 Max[j]=max(Max[j-1],dp[j]); 30 } 31 return *max_element(dp+m,dp+n+1); 32 }
HDU1024
•题意
给你 n 个数,将这 n 个数恰好划分成 m 个互不相交的子段,求这 m 个子段的最大值;
•题解
因为题目让求的是恰好 m 个互不相交的子段,那么,势必有 $m \le n$,不然是无法满足题意的;
直接按照上述分析的更新 m 次 $dp$ 值即可,最后,输出 $max(dp_m,dp_{m+1},\cdots ,dp_n)$ 即可;
•Code
51nod 1052
•题解
相比上一题,这道题的要求如果正整数的个数不足 m 个,直接输出这些正整数的加和即可;
只需要在上一题的基础上增加一个特判即可;
•Code
View Code1 #include<bits/stdc++.h> 2 using namespace std; 3 #define ll long long 4 #define INFll 0x3f3f3f3f3f3f3f3f 5 #define mem(a,b) memset(a,b,sizeof(a)) 6 const int maxn=5e3+50; 7 8 int n,m; 9 ll a[maxn]; 10 ll dp[maxn]; 11 ll Max[maxn]; 12 13 ll Solve() 14 { 15 ll sum=0; 16 int cnt=0; 17 for(int i=1;i <= n;++i) 18 if(a[i] > 0) 19 { 20 sum += a[i]; 21 cnt++; 22 } 23 if(m >= cnt) 24 return sum; 25 26 for(int i=1;i <= m;++i) 27 { 28 dp[i-1]=-INFll; 29 for(int j=i;j <= n-m+i;++j) 30 dp[j]=max(dp[j-1],Max[j-1])+a[j]; 31 Max[i-1]=-INFll; 32 for(int j=i;j <= n-m+i;++j) 33 Max[j]=max(Max[j-1],dp[j]); 34 } 35 return *max_element(dp+m,dp+n+1); 36 } 37 int main() 38 { 39 scanf("%d%d",&n,&m); 40 for(int i=1;i <= n;++i) 41 scanf("%lld",a+i); 42 printf("%lld\n",Solve()); 43 44 return 0; 45 }