给定由n个整数(可能为负)组成的序列a1、a2、a3...,an, 以及一个正整数m,要求确定序列的m个不相交子段,使这m个子段的总和最大。
注:有些题目要求非负数答案,当m子段最大和是负数时,取最大值为0.
思路:
二维dp,dp[i][j]表示必须以数组的第j个数结尾构成的i个子段的最大值。转移方程:dp[i][j] = max(dp[i][j-1],dp[i-1][t]),i-1<=t<j。解释:当需要第j个数加入时,构成i个子段最大和由以下两种情况的最大值中产生。1.前j-1个数已经构成i段,第j个数直接加入第i段,段数不变;2.前t个数已构成i-1段,t的范围[i-1,j),第j个数自成一段。
原始解法
#include<bits/stdc++.h> using namespace std; int main() { int n,m; cin>>n>>m; vector<int>v(n,0); for(int i=0;i<n;++i){ cin>>v[i]; } vector<vector<int>>dp(m+1,vector<int>(n+1,0)); //vector<vector<int>>dp(2,vector<int>(n+1,0)); for(int i=1;i<=m;++i) { int MAX = 0; for(int j=i;j<=n-m+i;++j) { // 之前使用j-1个数已经构成的i-1段的最大值 // 之前使用j-1个数 已经构成i段的最大值 // 上面两种情况构成的子段 均必须以第j-1个数结尾 // 构成第i段 第j个数或者自成一段 或者 加入第i段 // 两者取最大 MAX = max(MAX,dp[i-1][j-1]); dp[i][j] = max(MAX,dp[i][j-1])+v[j-1]; } } int ans = 0; for(int i=1;i<=N;++i) ans = max(ans,dp[M][i]); cout<<ans<<endl; return 0; }
观察发现,dp数组的更新只依赖当前行和上一行,故将dp数组行维度压缩成2,滚动更新。
#include<bits/stdc++.h> using namespace std; int main() { int n,m; cin>>n>>m; vector<int>v(n,0); for(int i=0;i<n;++i){ cin>>v[i]; } vector<vector<int>>dp(2,vector<int>(n+1,0)); for(int i=1,k=1;i<=m;++i,k^=1) { int MAX = 0; for(int j=i;j<=n-m+i;++j) { // 之前使用j-1个数已经构成的i-1段的最大值 // 之前使用j-1个数 已经构成i段的最大值 // 上面两种情况构成的子段 均必须以第j-1个数结尾 // 构成第i段 第j个数或者自成一段 或者 加入第i段 // 两者取最大 //MAX = max(MAX,dp[i-1][j-1]); //dp[i][j] = max(MAX,dp[i][j-1])+v[j-1]; MAX=max(MAX,dp[k^1][j-1]); //随时更新上一行最大值 dp[k][j]=max(dp[k][j-1],MAX)+v[j-1]; //对情况1、2的选择 } } int ans = 0; //for(int i=1;i<=N;++i) // ans = max(ans,dp[M][i]); for(int i=m;i<=n;i++) //找到第m行的最大值,即为答案 ans=max(ans,dp[m&1][i]); cout<<ans<<endl; return 0; }