Problem
给出一个有 n n n 个自然数的序列 { a n } \{a_n\} {an},给出一个常数 m m m。
现在要把这个序列分成若干段,每段的代价是 ( ∑ a i ) 2 + m (\sum a_i)^2+m (∑ai)2+m,求最小代价。
数据范围: 0 ≤ n ≤ 5 × 1 0 5 0 ≤ n ≤ 5\times10^5 0≤n≤5×105, 0 ≤ m ≤ 1000 0 ≤ m ≤ 1000 0≤m≤1000。
Solution
这是一道斜率优化 d p dp dp的板子题啦。
我们设 f ( i ) f(i) f(i) 表示处理到第 i i i 个数且从 i i i 切开的最小代价,显然有如下转移( S ( i ) S(i) S(i) 为前缀和):
f ( i ) = min j = 1 i − 1 { f ( j ) + ( S ( i ) − S ( j ) ) 2 + a i } f(i)=\min_{j=1}^{i-1}\{f(j)+(S(i)-S(j))^2+a_i\} f(i)=j=1mini−1{f(j)+(S(i)−S(j))2+ai}
但是转移是 n 2 n^2 n2 的,不能过 1 e 5 1e5 1e5 的数据,要想办法优化。
我们考虑对于 j , k ( k < j < i ) j,k(k<j<i) j,k(k<j<i),满足什么条件时用 j j j 转移比用 k k k 转移更优。即:
f ( j ) + ( S ( i ) − S ( j ) ) 2 + a i < f ( k ) + ( S ( i ) − S ( k ) ) 2 + a i f(j)+(S(i)-S(j))^2+a_i<f(k)+(S(i)-S(k))^2+a_i f(j)+(S(i)−S(j))2+ai<f(k)+(S(i)−S(k))2+ai
化简后得到:
f ( j ) − f ( k ) + S ( j ) 2 − S ( k ) 2 < 2 S ( i ) ( S ( j ) − S ( k ) ) f(j)-f(k)+S(j)^2-S(k)^2<2S(i)(S(j)-S(k)) f(j)−f(k)+S(j)2−S(k)2<2S(i)(S(j)−S(k))
又由于 j > k j>k j>k,且 S ( i ) S(i) S(i) 是单调不减的,所以有:
f ( j ) + S ( j ) 2 − ( f ( k ) + S ( k ) 2 ) S ( j ) − S ( k ) < 2 S ( i ) \frac{f(j)+S(j)^2-(f(k)+S(k)^2)}{S(j)-S(k)}<2S(i) S(j)−S(k)f(j)+S(j)2−(f(k)+S(k)2)<2S(i)
发现左边的那个式子像是斜率的计算式( k = y 2 − y 1 x 2 − x 1 k=\frac{y_2-y_1}{x_2-x_1} k=x2−x1y2−y1)。
于是把每个决策点的坐标写出来 ( S ( i ) , f ( i ) + S ( i ) 2 ) (S(i),f(i)+S(i)^2) (S(i),f(i)+S(i)2),在纸上推一推,不难发现我们要找的决策点在下凸壳上。
又由于 2 S ( i ) 2S(i) 2S(i) 是单调不减的,我们可以用单调队列维护这个下凸壳。
时间复杂度 O ( n ) O(n) O(n)。
Code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 500005
using namespace std;
int n,m,x,f[N],Q[N],S[N];
int Squ(int x) {return x*x;}
int X(int x) {return S[x];}
int Y(int x) {return f[x]+Squ(S[x]);}
//double slope(int x,int y) {return 1.0*(f[y]-f[x]+Squ(S[y])-Squ(S[x]))/(1.0*S[y]-S[x]);}
int main(){
while(~scanf("%d%d",&n,&m)){
memset(f,0,sizeof(f));
for(int i=1;i<=n;++i) scanf("%d",&x),S[i]=S[i-1]+x;
int l=0,r=0;
for(int i=1;i<=n;++i){
//while(l<r&&slope(Q[l],Q[l+1])<=2*S[i]) l++;
while(l<r&&Y(Q[l+1])-Y(Q[l])<=(X(Q[l+1])-X(Q[l]))*2*S[i]) l++;
f[i]=f[Q[l]]+Squ(S[i]-S[Q[l]])+m;
//while(l<r&&slope(Q[r-1],Q[r])>=slope(i,Q[r])) r--;
while(l<r&&(Y(Q[r])-Y(Q[r-1]))*(X(i)-X(Q[r]))>=(Y(i)-Y(Q[r]))*(X(Q[r])-X(Q[r-1]))) r--;
Q[++r]=i;
}
printf("%d\n",f[n]);
}
return 0;
}