最大M子段和dp + 滚动数组

Posted CzYoL

tags:

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

题目描述

给定 n 个数求这 n 个数划分成互不相交的 m 段的最大 m 子段和。

给出一段整数序列 A1,A2,A3,A4,...,Ax,...,An ,其中 1≤x≤n≤1,000,000, -32768≤Sx≤32767。

我们定义一种函数 sum(i,j)=Ai + ... + Aj (1≤i≤j≤n,且Ai~Aj 是连续的数)。

现在,我们得到一个正整数 m(1≤x≤m≤30),你的工作是寻找 m 对 i 与 j。这 m 对 i 和 j 满足以下条件:
  sum(i1,j1)+sum(i2,j2)+sum(i3,j3)+...+sum(im,jm)在这个序列中最大。
注意:任意两区间[ix,jx]和[iy,jy]的交集均为空集。

请你求出:最大的 sum(i1,j1)+sum(i2,j2)+sum(i3,j3)+...+sum(im,jm)是多少?

输入格式

第一行两个整数 n,m 。
第二行由空格隔开的 n 个整数,即 A1~An 。

输出格式

请输出我们定义的:sum(i1,j1)+sum(i2,j2)+sum(i3,j3)+...+sum(im,jm)的最大值,即最大的 m 子段和。

样例数据 1

输入

3 1 
1 2 3

输出

6

样例数据 2

输入

6 2 
-1 4 -2 3 -2 3

输出

8

题目分析

形如“求将x,分成y份的最*值”问题,通常dp都为 f[j][i],表示将前j个元素分成i份的最*值。

这道题也一样:f[j][i]如上所述,对于新加入的元素,有两种决策:

  1. 加到上一次的最后一段。
  2. 独立成段。

  转移方程就为:$f[j][i] = max(f[j - 1][i] + val[i], max_{k = 1}^{j - 1}\{f[j - 1][k]\} + val[i])$  

  稍微计算一下,会发现这样的空间已经报表。接下来就是滚动数组登场了。

  可以发现,转移方程中的j这一维总是从上一次的j-1转移,而i不变,而且f[j - 1][k]也只需要上一次的最大值即可,那么就可以使用滚动数组进行如下优化:

  1. 去掉i这一维, 并把i这一维提到外循环->保证j-1到j都是i这一维。 数组只需要f[N]

  2.用mx[j - 1]来表示$max_{k = 1}^{j - 1}\{f[j - 1][k]\} $, 每次内循环(j)更新, 以供下一个外循环(i)使用。

  于是转移变为:

for(int i = 1; i <= m; i++){
        ll tmp = -oo;
        for(int j = i; j <= n; j++){
            f[j] = max(f[j - 1] + val[j], mx[j - 1] + val[j]);
            tmp = max(tmp, f[j - 1]);
            mx[j - 1] = tmp;
        }
    }

 code

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<string>
#include<cmath>
using namespace std;

const int N = 1e6 + 5, oo = 0x7fffffff;
int n, m;
typedef long long ll;
ll f[N], mx[N], val[N];

inline ll read(){
    ll i = 0, f = 1; char ch = getchar();
    for(; (ch < 0 || ch > 9) && ch != -; ch = getchar());
    if(ch == -) f = -1, ch = getchar();
    for(; ch >= 0 && ch <= 9; ch = getchar())
        i = (i << 3) + (i << 1) + (ch - 0);
    return i * f;
}

inline void wr(ll x){
    if(x < 0) putchar(-), x = -x;
    if(x > 9) wr(x / 10);
    putchar(x%10+0);
}

int main(){
    n = read(), m = read();
    for(int i = 1; i <= n; i++) val[i] = read();
    for(int i = 1; i <= m; i++){
        ll tmp = -oo;
        for(int j = i; j <= n; j++){
            f[j] = max(f[j - 1] + val[j], mx[j - 1] + val[j]);
            tmp = max(tmp, f[j - 1]);
            mx[j - 1] = tmp;
        }
    }
    ll ans = -oo;
    for(int i = m; i <= n; i++)
        ans = max(ans, f[i]);
    wr(ans), putchar(\n);
    return 0;
}

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

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

51Nod 1050 循环数组最大子段和 dp

1052 最大M子段和(DP)

51nod 1050 循环数组最大子段和环形DP/最大子段和/正难则反

环形数组 最大子段和 dp

最大子段和之M子段和