BZOJ 3675: [Apio2014]序列分割

3675: [Apio2014]序列分割

Description

小H最近迷上了一个分隔序列的游戏。在这个游戏里,小H需要将一个长度为n的非负整数序列分割成k+1个非空的子序列。为了得到k+1个子序列,小H需要重复k次以下的步骤:
1.小H首先选择一个长度超过1的序列(一开始小H只有一个长度为n的序列——也就是一开始得到的整个序列);
2.选择一个位置,并通过这个位置将这个序列分割成连续的两个非空的新序列。
每次进行上述步骤之后,小H将会得到一定的分数。这个分数为两个新序列中元素和的乘积。小H希望选择一种最佳的分割方式,使得k轮之后,小H的总得分最大。

Input

输入第一行包含两个整数n,k(k+1≤n)。

第二行包含n个非负整数a1,a2,...,an(0≤ai≤10^4),表示一开始小H得到的序列。

Output

输出第一行包含一个整数,为小H可以得到的最大分数。

Sample Input

7 3
4 1 3 4 0 2 3

Sample Output

108

思路:

  Dp,斜率优化。

  首先考虑正常的方程F[i][j] 表示在前i个点切j次,在i点切最后一次。首先要思考好如果切割方案一样,切割顺序和答案没有关系,这样我们就可以比较容易的得出转移方程:f[i][j] = max(f[k][j-1]+(s[i]-s[j])*(s[n]-s[i])); 考虑斜率优化,这样的状态我们显然是无法斜率优化的,所以尝试把第二维拉到前面 这样

f[i][j] 就只和f[i-1][k]有关,可以把第一维压掉,进行斜率优化,设g[j]为滚动数组,则新的转移方程为 :f[i] =max(g[j]+(s[i]-s[j])*(s[n]-s[i]));化简开得f[i] + si*si - si*sn = g[j] - sjsn +sisj; 这样把和i有关的设为Y,和j有关的设为b,把sj设为k,si即为x。Y = kx+b;

因为s单调递增 所以各种单调递增。

 

考虑对队首的维护,若Y(j,q[l]) < Y(j,q[l+1])弹出队首

考虑对队尾的维护,当队尾第二个元素与j构成的斜率小于队尾斜率时,弹出队尾。

其余的直接更新即可。

把方程化成Y = KX + B的形式免去了斜率优化中的除法。常数较小

以下是代码

=#include <cstdio>
#include <cstring>
using namespace std;
#define ll long long
const int N = 100005;
#define K(i) (s[i])
#define B(i) (g[i]-s[i]*s[i])
#define Y(i,j) (K(j)*s[i]+B(j))
int q[N],l,r;
ll g[N],f[N],s[N];
bool cmp(int i,int j,int k) {
    ll x=(K(i)-K(k))*(B(j)-B(i));
    ll y=(K(i)-K(j))*(B(k)-B(i));
    return x>=y;
}
int main() {
    int n,k,i,x,j;
    scanf("%d%d",&n,&k);
    for(i=1;i<=n;i++) {
        scanf("%d",&x);
        s[i]=s[i-1]+x;
    }
    for(i=1;i<=k;i++) {
        l=r=0;
        for(j=i;j<=n;j++) {
            while(l<r&&Y(j,q[l])<Y(j,q[l+1]))l++;
            f[j]=Y(j,q[l]);
            while(l<r&&cmp(j,q[r-1],q[r]))r--;
            q[++r]=j;
        }
        for(j=i;j<=n;j++) {
            g[j]=f[j];
        }
    }
    printf("%lld\n",f[n]);
}

 欢迎来原博客看看>原文链接<

posted @ 2018-05-30 20:15  TOBICHI_ORIGAMI  阅读(56)  评论(0编辑  收藏  举报