原文链接http://www.cnblogs.com/zhouzhendong/p/8688187.html
题目传送门 - BZOJ3156
题意
长为$n$的序列$A$划分,设某一段为$[i,j]$,则其花费为$A_j+sum_{k=i}^{j}(j-k)$。
一种划分方式的花费就是他每一段的花费和。
最小化花费。
$nleq 10^6$
题解
斜率优化裸题。
设$dp_i$表示序列前$i$项通过划分可以到的最小花费。
则
$$dp_i=min{dp_j+a_i+frac{(i-j)(i-j-1)}{2}}(0leq j<i)$$
按照套路化简。
得:
$$dp_j+a_i+frac{(i-j)(i-j-1)}{2}\=dp_j+a_i+frac{j^2}2+frac j2+frac{i^2}2-frac i2-ij$$
设$x_i=i,y_i=dp_i+frac{i^2}2+frac i2$,
则原式
$$=y_j-ix_j+frac{i^2}2-frac i2$$
假设$k<j$且从$k$转移比$j$劣,那么:
$$y_j-ix_j+frac{i^2}2-frac i2<y_k-ix_k+frac{i^2}2-frac i2$$
$$Longrightarrow y_j-ix_j<y_k-ix_k$$
$$Longrightarrow frac{y_j-y_k}{x_j-x_k}<i$$
然后就是套路(其实前面也是套路……)
参见这里
但是这里为了避免精度问题,我们在计算$x$和$y$的时候都乘个$2$就可以了。
代码
#include <bits/stdc++.h> using namespace std; typedef long long LL; const int N=1000005; int n,q[N],head=1,tail=0; LL a[N],id[N],dp[N],x[N],y[N]; int main(){ scanf("%d",&n); for (int i=1;i<=n;i++) scanf("%lld",&a[i]); q[++tail]=0; for (int i=1;i<=n;i++){ int j=q[head+1],k=q[head]; while (tail-head>0&&y[j]-y[k]<=(x[j]-x[k])*i) head++,j=q[head+1],k=q[head]; j=k; dp[i]=dp[j]+a[i]+1LL*(i-j-1)*(i-j)/2; x[i]=i*2; y[i]=dp[i]*2+i+1LL*i*i; j=q[tail],k=q[tail-1]; while (tail-head>0&&(y[i]-y[j])*(x[j]-x[k])<=(y[j]-y[k])*(x[i]-x[j])) tail--,j=q[tail],k=q[tail-1]; q[++tail]=i; } printf("%lld",dp[n]); return 0; }