Problem
给出一个长度为 n n n 的序列 { a n } \{a_n\} {an} 以及常数 m m m,现要将它分成若干段,对于一个 [ i , j ] [i,j] [i,j] 的区间的贡献是 ( j − i + ∑ k = i j a k − m ) 2 (j-i+\sum\limits_{k=i}^ja_k-m)^2 (j−i+k=i∑jak−m)2,求总贡献的最小值。
数据范围: 1 ≤ n ≤ 50000 1\le n\le50000 1≤n≤50000, 1 ≤ n , a i ≤ 1 0 7 1\le n,a_i\le10^7 1≤n,ai≤107。
Solution
我们定义 S i S_i Si 为 a i a_i ai 的前缀和, f i f_i fi 为在 i i i 到 i + 1 i+1 i+1 分割一次的最小费用。
那么有
f i = min j = 1 i − 1 { f j + ( i − j − 1 + ( S i − S j ) − m ) 2 } f_i=\min_{j=1}^{i-1}\{f_j+(i-j-1+(S_i-S_j)-m)^2\} fi=j=1mini−1{fj+(i−j−1+(Si−Sj)−m)2}
那我们令 G i = S i + i G_i=S_i+i Gi=Si+i(为了方便化简),得:
f i = min j = 1 i − 1 { f j + ( G i − G j − ( m + 1 ) ) 2 } f_i=\min_{j=1}^{i-1}\{f_j+(G_i-G_j-(m+1))^2\} fi=j=1mini−1{fj+(Gi−Gj−(m+1))2}
于是选择任意的 k < j < i k<j<i k<j<i,当 j j j 比 k k k 优时,有(接下来就是把平方拆开然后化简,就不写了):
( f j + G j 2 + 2 ( m + 1 ) G j ) − ( f k + G k 2 + 2 ( m + 1 ) G k ) G j − G k < 2 G i \frac{(f_j+G_j^2+2(m+1)G_j)-(f_k+G_k^2+2(m+1)G_k)}{G_j-G_k}<2G_i Gj−Gk(fj+Gj2+2(m+1)Gj)−(fk+Gk2+2(m+1)Gk)<2Gi
然后维护一个下凸壳转移即可。
Code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 50005
#define ll long long
using namespace std;
int n,m,Q[N];
ll S[N],G[N],f[N];
ll Squ(ll x) {return x*x;}
ll ordi(int x) {return f[x]+Squ(G[x])+2ll*(m+1)*G[x];}
double slope(int x,int y) {return 1.0*(ordi(y)-ordi(x))/(G[y]-G[x]);}
int main(){
scanf("%d%d",&n,&m);
for(int i=1,x;i<=n;++i){
scanf("%d",&x),S[i]=S[i-1]+x,G[i]=S[i]+i;
}
int l=0,r=0;
for(int i=1;i<=n;++i){
while(l<r&&slope(Q[l],Q[l+1])<=2*G[i]) l++;
f[i]=f[Q[l]]+Squ(G[i]-G[Q[l]]-(m+1));
while(l<r&&slope(Q[r-1],Q[r])>=slope(Q[r],i)) r--;
Q[++r]=i;
}
printf("%lld\n",f[n]);
return 0;
}