题目
纪念不看题解(A)掉的第一个斜率优化dp:P3195 [HNOI2008]玩具装箱TOY
一个月后补的重点:斜率优化的本质就是利用推出来的公式决定维护一个凸包,用单调队列来维护这个凸包,因为我们假定(j<k,dp_j>dp_k),所以每次决策显然就取队头,从而得到最优解
做法
定义数组(sum_i)为长度前缀和,数组(dp_i)为前(i)个玩具的最小花费
当前块末(i)与前一块末(j)状态转移为(dp_i=dp_{j-1}+i-(j+1)+sum_i-sum_j-L)
假设(j<k),从(k)转移比(j)转移更优:
(dp_j+[i-(j+1)+sum_i-sum_j-L]^2>)
(dp_k+[i-(k+1)+sum_i-sum_k-L]^2)
(Longrightarrow)
(dp_j+[(i+sum_i-L)+(-j-1-sum_j)]^2>)
(dp_k+[(i+sum_i-L)+(-k-1-sum_k)]^2)
(Longrightarrow)
(dp_j+(i+sum_i-L)^2+2(i+sum_i-L)(-j-1-sum_j)+(-j-1-sum_j)^2>)
(dp_k+(i+sum_i-L)^2+2(i+sum_i-L)(-k-1-sum_k)+(-k-1-sum_k)^2)
(Longrightarrow)
(dp_j+2(i+sum_i-L)(-j-1-sum_j)+(-j-1-sum_j)^2>)
(dp_k+2(i+sum_i-L)(-k-1-sum_k)+(-k-1-sum_k)^2)
(Longrightarrow)
(dp_j-dp_k+(-j-1-sum_j)^2-(-k-1-sum_k)^2>)
((i+sum_i-L) {2[(-k-1-sum_k)-(-j-1-sum_j)]})
(Longrightarrow)
(dfrac{dp_j-dp_k+(-j-1-sum_j)^2-(-k-1-sum_k)^2}{2[(-k-1-sum_k)-(-j-1-sum_j)]}>i+sum_i-L)
:然后你就会样例都过不了,为什么?根据单调性(2[(-k-1-sum_k)-(-j-1-sum_j)])为负,符号要变,所以最后的式子为
(dfrac{dp_j-dp_k+(-j-1-sum_j)^2-(-k-1-sum_k)^2}{2[(-k-1-sum_k)-(-j-1-sum_j)]}<i+sum_i-L)
(i+sum_i-L)单调递增,所以我们用单调队列维护上凸包
My complete code
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL maxn=200000;
inline LL Read(){
LL x=0,f=1; char c=getchar();
while(c<'0'||c>'9'){
if(c=='-') f=-1; c=getchar();
}
while(c>='0'&&c<='9')
x=(x<<3)+(x<<1)+c-'0',c=getchar();
return x*f;
}
LL n,L,head,tail;
LL sum[maxn],dp[maxn],q[maxn];
inline double Get(LL i){
return (double)i+sum[i]-L;
}
inline LL P_(LL x){
return x*x;
}
inline double Xl(LL j,LL k){
return (double)
(dp[j]-dp[k]+P_(-j-1-sum[j])-P_(-k-1-sum[k]))/(2*(-k-1-sum[k]+j+1+sum[j]));
}
int main(){
n=Read(),L=Read();
for(LL i=1;i<=n;++i)
sum[i]=sum[i-1]+Read();
dp[1]=P_(sum[1]-L),
q[head=tail=1]=1;
for(LL i=2;i<=n;++i){
while(head<tail&&Xl(q[head],q[head+1])<Get(i))
++head;
LL j=q[head];
LL val=i-(j+1)+sum[i]-sum[j]-L;
dp[i]=min(dp[j]+P_(val),P_(i-1+sum[i]-L));
while(head<tail&&Xl(q[tail-1],q[tail])>Xl(q[tail],i))
--tail;
q[++tail]=i;
}
printf("%lld",dp[n]);
return 0;
}