[斜率优化][DP]luogu P3648 序列分割

https://www.luogu.org/problemnew/show/P3648

分析

首先我们先证明,分割的顺序与答案无关

设有三段a,b,c

a(b+c)+bc=ab+ac+bc①

ab+(a+b)c=ab+ac+bc②

①=②

假装证明了

然后我们设f[i][k]表示第i个数字的后面分割,共分割了k次的最大分数

f[i][k]=min(f[j][k-1]+s[j]*(s[i]-s[j])) (j<i) s为前缀和

这个比较显然的斜率优化柿子

单调队列维护一个下凸壳即可

 

#include <iostream>
#include <cstdio>
#include <memory.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int h,t,q[N];
ll f[N][2],s[N],from[310][N];
int n,k;

double XL(int i,int j,int k) {
    if (s[k]-s[j]==0) return -2147483647;
    return 1.0*(f[j][(i+1)&1]-s[j]*s[j]-f[k][(i+1)&1]+s[k]*s[k])/(s[k]-s[j]);
}

int main() {
    scanf("%d%d",&n,&k);
    for (int i=1;i<=n;i++) scanf("%lld",&s[i]),s[i]+=s[i-1];
    for (int j=k-1;j>=0;j--) {
        q[h=t=1]=0;
        for (int i=1;i<=n;i++) {
            while (h<t&&XL(j,q[h],q[h+1])<=s[i]) h++;
            f[i][j&1]=f[q[h]][(j+1)&1]+s[q[h]]*(s[i]-s[q[h]]);
            from[j][i]=q[h];
            while (h<t&&XL(j,q[t-1],q[t])>=XL(j,q[t],i)) t--;
            q[++t]=i;
        }
    }
    int x=n,j=0;
    printf("%lld\n",f[n][0]);
    while (j<k) {
        x=from[j][x];
        printf("%d ",x);
        j++;
    }
}
View Code

 

posted @ 2019-07-06 07:27  Vagari  阅读(173)  评论(0编辑  收藏  举报