P3195 [HNOI2008]玩具装箱


直线的斜率是2*a[i],用一个已知斜率的直线去向上平移。当前面的线段斜率小于2*a[i]时,截距不是最小的,是要淘汰的(弹出队头),直到碰到第一条斜率大于2*a[i]的点(假设p<q,斜率(p,q)>2*a[i]),这个点(b[j],dp[j]+b[j]^2)即p点,使得dp[i]最小,如上图。
随着a[i]是单调递增的,后面的点继续入队,作为后备力量,前面的点会随a[i]增大而逐渐淘汰。
如果是凹进去的点,从图形上看出直线平移的过程,凹进去的点没有优势,根据斜率比较判断是否为凹点,队尾弹出。
总结:假设k<p<q,队首出队条件:斜率slope(k,p)<2*a[i],k出队; 队尾出队条件:slope(k,p) > 2*a[i] 且slope(p,q) < 2*a[i], p出队尾。
#include <bits/stdc++.h> using namespace std; typedef double db; typedef long long LL; const int maxn=50010; int n,L; db sum[maxn],dp[maxn]; int head,tail,Q[maxn]; inline db a(int i){return sum[i]+i;} inline db b(int i){return a(i)+L+1;} inline db X(int i){return b(i);} inline db Y(int i){return dp[i]+b(i)*b(i);} inline db slope(int i,int j){return (Y(i)-Y(j))/(X(i)-X(j));} int main(){ scanf("%d%d",&n,&L); for(int i=1;i<=n;i++){ scanf("%lf",&sum[i]); sum[i]+=sum[i-1]; } head=tail=1; for(int i=1;i<=n;i++){ while(head<tail&&slope(Q[head],Q[head+1])<2*a(i)) ++head; dp[i]=dp[Q[head]]+(a(i)-b(Q[head]))*(a(i)-b(Q[head])); while(head<tail&&slope(i,Q[tail-1])<slope(Q[tail-1],Q[tail])) --tail; Q[++tail]=i; } printf("%lld\n",(LL)dp[n]); return 0; }
浙公网安备 33010602011771号