[斜率优化dp] HDU 3507 Print Article
题目大意
给你一个序列 \(\{c_n\}\),以及一个正整数 \(M\),现在要将这个序列分割成连续的若干段,每一段的价值是 \(\left(\sum_{i=1}^{k}c_i\right)^2+M\),求最小价值。\((n\leq 500000,M\leq 1000)\)
题解
令 \(Sum[n]=\sum_{i=1}^{n}c_i\),
设 \(dp[i]\) 表示把前 \(i\) 个数分割成若干段的最小价值,那么有 \(dp[i]=\min\{dp[j]+\left(Sum[i]-Sum[j]\right)^2+M\},j<i\)。
整理后得 \(dp[j]+Sum[j]^2=2Sum[i]Sum[j]+dp[i]-Sum[i]^2-M\),
发现 \(Sum[i]\) 单增,可以斜率优化。
令 \(y=dp[j]+Sum[j]^2,k=2Sum[i],x=Sum[j]\),
原式转化成了 \(y=kx+dp[i]-Sum[i]^2-M\) ,因为要使dp值最小,所以相当于要使这条直线在 \(y\) 轴上的截距最小,使用单调队列维护一个下凸壳进行状态转移即可。时间复杂度 \(O(n)\)。
Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <cstdio>
#include <vector>
using namespace std;
#define RG register int
#define LL long long
template<typename elemType>
inline void Read(elemType &T){
elemType X=0,w=0; char ch=0;
while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
T=(w?-X:X);
}
LL dp[500005],Sum[500005];
int Q[500005];
int N,head,tail;LL M;
inline LL x(int i){return Sum[i];}
inline LL y(int i){return dp[i]+Sum[i]*Sum[i];}
inline LL k(int i){return Sum[i]<<1;}
inline void maintain(int i,int j){
dp[i]=dp[j]+(Sum[i]-Sum[j])*(Sum[i]-Sum[j])+M;
}
LL Solve(){
head=1;tail=0;
Q[++tail]=0;
for(RG i=1;i<=N;++i){
while(tail-head+1>=2){
int a=Q[head],b=Q[head+1];
if(y(b)-y(a)<=k(i)*(x(b)-x(a))) ++head;
else break;
}
maintain(i,Q[head]);
while(tail-head+1>=2){
int a=Q[tail-1],b=Q[tail];
if((y(b)-y(a))*(x(i)-x(a))>=(y(i)-y(a))*(x(b)-x(a))) --tail;
else break;
}
Q[++tail]=i;
}
return dp[N];
}
int main(){
while(~scanf("%d%lld",&N,&M)){
memset(dp,0x3f,sizeof(dp));
dp[0]=0;
for(RG i=1;i<=N;++i){
Read(Sum[i]);
Sum[i]+=Sum[i-1];
}
printf("%lld\n",Solve());
}
return 0;
}