时间限制:1000MS 代码长度限制:10KB
提交次数:0 通过次数:0
题型: 编程题 语言: G++;GCC;VC;JAVA
“最大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
定义二维数组 dp, dp[ i ][ j ],表示前 j 项所构成 i 子段的最大和,且必须包含着第 j 项,即以第 j 项结尾
求 dp[ i ][ j ],有两种情况
对于第二点,思考方式可以是如下图:当你遍历到6这个元素时,很明显不能选择前面的元素(因为是负的),那么就选择其他(尽量正数),于是目光自然就看到了前面那一份 2 + 3 = 5。
而 k 的取值范围又如何解释呢:
- 大于等于 i - 1 ,是因为 i - 1 个数字最多只能被分为 i - 1 份,即每份一个数字;
- 而小于 j 即最多可以选择到第 j - 1 个数字,是因为当你在抉择第 j 个数字如何划分范围时,前面只有 j - 1 个数字给你划分,范围怎么可能能跑到 j 自己及后面去。
然后比较上面两种情况,取大的即可。
下面看图,红色数字为输入的序列:
如图,要求dp[ 3 ][ 6 ],只需比较 他左边的那个,和上面那一行圈起来的之中 最大的数(i-1 <= t <= j),
再加上 a[ j ] 即为 dp[ 3 ][ 6 ] 的值。
在划分为 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]); }
时间复杂度大致为O(m*(n-m+1)),mn-m方
通过图片,分析情况1和2,就能发现,从左上角走到第 m 行的最后一个元素即可,找出第 m 行的最大值即为答案。
更多注释可查看下方的完整代码中,有助于理解
#include
#include
#include
#include
/*
6 3
2 3 -7 6 4 -57 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;
}
#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);}
}
对我感兴趣的小伙伴可查看以下链接