【BZOJ3675】【Apio2014】序列分割

Description

  
  传送门
  
  
  
    
  

Solution

  
​  之前我也遇到过一次这种“两段之和乘积作为贡献“的问题:考虑把这一种\((\sum) *(\sum)\)的形式拆括号,就可以发现贡献其实就是分别处于左右的两两元素乘积之和。
  
​  题目的分割\(k\)次,其实就是要你把序列分成\(k+1\)段。
  
​  再考虑在题目中如此的分割方法下,贡献是怎么产生的。对于每一个元素\(a_i\),每次分割时,若涉及到自己,则会贡献一定的乘积\(a_i(\sum)\)。细心想一想就会发现,这个\(\sum\)的总值就是在最终方案下不处于\(a_i\)所在段的元素之和。
  
  单向考虑每一项乘积,(这里有点跳)总的来算,每一段\([l,r]\)的贡献就是\((\sum_{i=l}^ra_i)(\sum_{i=1}^{l-1}a_i)\)。记\(a\)的前缀和为\(s\),则贡献是\((s_r-s_{l-1})s_{l-1}\).
  
  我们可以写出DP式,\(f_{i,j}\)表示前\(i\)个数恰好分成\(j\)段的贡献最大值:
\[ f_{i,j}=\max\{f_{k,j-1}+(s_i-s_k)s_k\}\;\;(k<i) \]
   
  
  直接DP是\(\mathcal O((k+1)n^2)\)的。而看到这个式子比较简单,考虑斜率优化。这里省去第二维,整体做\(k+1\)次即可。
  
​  设\(k<j<i\)\(j\)\(k\)优,则有:
\[ \begin{aligned} f_j+(s_i-s_j)s_j&>f_{k}+(s_i-s_k)s_k\\ f_j+s_is_j-s_j^2&>f_k+s_is_k-s_k^2\\ s_i(s_j-s_k)&>(s_j^2-f_j)-(s_k^2-f_k)\\ \frac{(s_j^2-f_j)-(s_k^2-f_k)}{(s_j-s_k)}&<s_i \end{aligned} \]
  直接做就可以了。
  
  
  
  
  

Code

  

#include <cstdio>
#include <iostream>
using namespace std;
typedef long long ll;
const int N=100005;
const ll INF=1LL<<62;
const double EPS=1e-6;
int n,m,a[N];
int q[N],head,tail;
ll s[N],real_f[N],real_g[N],*f=real_f,*g=real_g;
inline double slope(int a,int b){
    return 1.0*((s[b]*s[b]-g[b])-(s[a]*s[a]-g[a]))/(s[b]-s[a]);
}
int main(){
    scanf("%d%d",&n,&m);
    int cnt=0;
    for(int i=1,x;i<=n;i++){
        scanf("%d",&x);
        if(x)
            cnt++,s[cnt]=s[cnt-1]+x;
    }
    n=cnt; m=min(m,n-1)+1;
    for(int j=2;j<=m;j++){
        q[head=tail=1]=j-1;
        for(int i=j;i<=n;i++){
            while(head<tail&&slope(q[head],q[head+1])-EPS<s[i]) head++;
            int best=q[head];
            f[i]=g[best]+(s[i]-s[best])*s[best];    
            while(head<tail&&slope(q[tail-1],q[tail])>slope(q[tail],i)) tail--;
            q[++tail]=i;
        }
        swap(f,g);
    }
    printf("%lld\n",g[n]);
    return 0;
}

转载于:https://www.cnblogs.com/RogerDTZ/p/9248746.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值