BZOJ 1010: [HNOI2008]玩具装箱toy

传送门

斜率优化DP入门题

首先有一个很显然的做法

设 $f[i]$ 表示当前以第 i 个节点作为一段区间的结尾时,从 1 到 i 的最小花费

然后枚举上一个作为结尾的物品的编号为 j

那么转移就直接根据题意求就行了

设长度的前缀和为 $sum$,那么花费就是 $(i-j-1+sum[i]-sum[j]-L)^2$

那么转移方程为 $f[i]=f[j]+(i-j-1+sum[i]-sum[j]-L)^2$

那么 $f[i]=f[j]+((i+sum[i])-(j+sum[j]+1+L))^2$

设 $A[i]=i+sum[i],B[i]=A[i]+1+L$

那么 $f[i]=f[j]+A[i]^2-2A[i]B[j]+B[j]^2$

所以 $2A[i]B[j]+(f[i]-A[i]^2)=f[j]+B[j]^2$

设 $y=f[j]+B[j]^2,k=2A[i],x=B[j],b=(f[i]-A[i]^2)$

那么上式就变成了 $kx+b=y$ 的样子

对于同一个 $i$,$k$ 是不变的,$A[i]^2$ 也是不变的,对于同一个 $i$ ,如果确定了直线 $y=kx+b$ 经过的一个点就确定了此直线

所以如果我们能找到一个点 $(x_j,y_j)$,使得直线 $y=kx+b$ 的截距 $b$ 最小,那么此时的 $f[i]$ 也就取最小值

对当前每个点 $(x,y)$ 维护一个下凸包,因为显然 $b$ 取最小时直线一定经过的是下凸包上的点,自己画个图就好了

然后可以发现随着 $i$ 的上升,$k$ 是不降的,所以 $f[i]$ 也是满足单调不降的,所以直接用单调队列维护凸包内的点就好了(这个同样画个图就懂了)

注意long long 

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
typedef double db;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x*f;
}
const int N=5e4+7;
int n,L,c[N];
ll sum[N],f[N];
int q[N],hea,las;
inline ll a(int x) { return sum[x]+x; }
inline ll b(int x) { return a(x)+L+1; }
inline ll X(int x) { return b(x); }
inline ll Y(int x) { return f[x]+b(x)*b(x); }
inline db slope(int i,int j) { return (db)(Y(i)-Y(j))/(X(i)-X(j)); }
int main()
{
    n=read(); L=read();
    for(int i=1;i<=n;i++) c[i]=read(),sum[i]=sum[i-1]+c[i];
    hea=las=1;
    for(int i=1;i<=n;i++)
    {
        while(hea<las&&slope(q[hea],q[hea+1])<2*a(i)) hea++;
        f[i]=f[q[hea]]+(a(i)-b(q[hea]))*(a(i)-b(q[hea]));
        while(hea<las&&slope(i,q[las-1])<slope(q[las-1],q[las])) las--;
        q[++las]=i;
    }
    cout<<f[n];
    return 0;
}

 

posted @ 2018-12-24 08:14  LLTYYC  阅读(154)  评论(0编辑  收藏  举报