bzoj1010——斜率优化DP

Description

  P教授要去看奥运,但是他舍不下他的玩具,于是他决定把所有的玩具运到北京。他使用自己的压缩器进行压
缩,其可以将任意物品变成一堆,再放到一种特殊的一维容器中。P教授有编号为1…N的N件玩具,第i件玩具经过
压缩后变成一维长度为Ci.为了方便整理,P教授要求在一个一维容器中的玩具编号是连续的。同时如果一个一维容
器中有多个玩具,那么两件玩具之间要加入一个单位长度的填充物,形式地说如果将第i件玩具到第j个玩具放到一
个容器中,那么容器的长度将为 x=j-i+Sigma(Ck) i<=K<=j 制作容器的费用与容器的长度有关,根据教授研究,
如果容器长度为x,其制作费用为(X-L)^2.其中L是一个常量。P教授不关心容器的数目,他可以制作出任意长度的容
器,甚至超过L。但他希望费用最小.

Input

  第一行输入两个整数N,L.接下来N行输入Ci.1<=N<=50000,1<=L,Ci<=10^7

Output

  输出最小费用

Sample Input

5 4

3

4

2

1

4
Sample Output

1


这道题是一道DP题,应该是十分显而易见的,而且状态转移方程也十分的容易想到,即
f[i]=max(f[j]+(sum[i]-sum[j]+i-j-1-l)²)  [0<=j<i]
//sum为前缀和数组
但是数据范围有50000,如果直接用N²的DP会超时,所以我们需要进行优化。
我们设s[i]=sum[i]+i,则
//设j为某一时刻最优值
f[i]=f[j]+(s[i]-s[j]-l-1)²;
//我们先令l++,则
f[i]=f[j]+(s[i]-s[j]-l)²;
f[i]=f[j]+s[i]²+(s[j]+l)²-2*s[i]*(s[j]+l);
f[i]+2*s[i]*(s[j]+l)=f[j]+s[i]²+(s[j]+l)²
//  b +  k * x  =  y;
//所以我们发现f[i]是函数截距b,k为2*s[i],y为f[j]+s[i]²+(s[j]+l)²,x为(s[j]+l)。
//对于j点的坐标(s[j]+l,f[j]+s[i]²+(s[j]+l)²)。
//所以我们只需要求出最小截距b,就可以求出最小f[i],也就是答案  
首先确定直线斜率的变化特点。由于k=s[i]*2所以我们获得两个重要信息,即k>0并且k随i单调递增。
所以我们可以维护一个下凸包,而单调队列可以正好维护这个凸包。(可能讲到这里还是十分懵懂,所以我们画一张图来帮助理解)

这里写图片描述

我们维护了黑边的一个凸包,现在我们新来了一个点i,它的斜率直线就是蓝线(k),我们发现最下面的边(也就是下面被红叉叉删掉的边)斜率小于k,所以一最下面的边维护处的b值一定小于蓝边的k值,所以最下面的边已经没有任何意义了,可以直接删去。
我们再考虑上面的边,如果一条边的斜率大于点i与该点相连线(绿色的线)的斜率,那么这条边就没有意义了(它维护的b值肯定小于绿线的值,所以可以删去)。
这样我们就可以用单调队列维护,删去这些点后,将i放到队头,继续维护下一个值。
关于斜率优化DP,有位DALAO写的非常好
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll read(){
    char c;ll x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
    while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
}
ll sum[50006],s[50006],f[50006],q[50006],w,r,l,n;
double sqr(double x){return x*x;}
double xx(ll i){return s[i];}
double yy(ll i){return f[i]+(s[i]+l-1)*(s[i]+l-1);}
double k(ll i,ll j){return (yy(j)-yy(i))/(xx(j)-xx(i));}
int main()
{
    n=read();l=read();l++;
    for(ll i=1;i<=n;i++){
        ll x=read();
        sum[i]=sum[i-1]+x;
        s[i]=sum[i]+i;
        f[i]=1e18;
    }
    w=r=0;
    for(ll i=1;i<=n;i++){
        while(r<w&&k(q[r],q[r+1])<s[i]*2) r++;
        f[i]=f[q[r]]+sqr(s[i]-s[q[r]]-l);
        //f[i]=f[j]+(s[i]-s[j]-L)*(s[i]-s[j]-L);
        while(r<w&&k(q[w-1],q[w])>k(q[w],i)) w--;
        q[++w]=i;
    }
    printf("%lld",f[n]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值