Jeanny
寂兮,寥兮,独立不改,周行而不殆

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;
}

 

 

 

posted on 2022-02-18 08:04  Jeanny  阅读(57)  评论(0)    收藏  举报