P3648 [APIO2014] 序列分割

//URL:
/*
你正在玩一个关于长度为 nn 的非负整数序列的游戏。这个游戏中你需要把序列分成 k+1k+1 个非空的块。为了得到 k+1k+1 块,你需要重复下面的操作 kk 次:
选择一个有超过一个元素的块(初始时你只有一块,即整个序列)
选择两个相邻元素把这个块从中间分开,得到两个非空的块。
每次操作后你将获得那两个新产生的块的元素和的乘积的分数。你想要最大化最后的总得分

在确定切断位置时 顺序不影响最后的答案 abc显然 数学归纳法
f[i][j] 前i个数 分成j份 max
f[i][j]=max(f[i][j],f[l][j-1]+sum[l]*(sum[i]-sum[l]))
若i<j 且j更优 则
(gj-sj^2)-(gk-sk^2)/sk-sj <=si
因为计算s时是正序 所以维护递增的序列即可 

若i<j且 i更优 则
fi-si^2-(fj-sj^2)/-si-(-sj) >si
仍然正序 维护递增 但是点(-si,fi-si^2)

:::单调队列只是判断最优解 的一种方式而已
:::若si==sj 即aj==0 直接返回-inf不影响最后结果 否则难以选择return值
*/
/*
7 3
4 1 3 4 0 2 3

108
1 3 5
*/
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<string.h>
#include<queue>
#include<vector>
#include<bits/stdc++.h>
typedef long long ll;
#define ddd printf("-----------------------\n");
using namespace std;
const int N=1e5+5,M=205;
int n,k,a[N],q[N],pre[N][M],ans[N];
long long s[N],f[N],g[N];

double slope(int i,int j) {
    if(s[i]==s[j]) return -1e18;
    return 1.0*((g[i]-s[i]*s[i])-(g[j]-s[j]*s[j]))/(s[j]-s[i]);
}
int main() {
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;++i) scanf("%d",&a[i]),s[i]=s[i-1]+a[i];
    for(int j=1;j<=k;++j) {
        int l=1,r=0;
        q[++r]=0;
        for(int i=1;i<=n;++i) {
            while(l<r&&slope(q[l],q[l+1])<=s[i]) ++l;
            f[i]=g[q[l]]+s[q[l]]*(s[i]-s[q[l]]);
            pre[i][j]=q[l];
            while(l<r&&slope(q[r-1],q[r])>=slope(q[r],i)) --r;
            q[++r]=i;
        }
        memcpy(g,f,sizeof(f));
    }
    printf("%lld\n",f[n]);
    for(int x=n,i=k;i>=1;--i) x=pre[x][i],ans[i]=x;
    //for(int x=n,i=k;i>=1;--i) x=pre[x][i],printf("%d%c",x," \n"[i==1]);
    for(int i=1;i<=k;i++) cout<<ans[i]<<" ";
    cout<<'\n';
    
    return 0;
}

 

posted @ 2023-12-30 12:53  JMXZ  阅读(14)  评论(0)    收藏  举报