bzoj 1010: [HNOI2008] 玩具装箱 toy [斜率优化dp] [动态规划]

1010: [HNOI2008]玩具装箱toy

Time Limit: 1 Sec Memory Limit: 162 MB
Submit: 9202 Solved: 3678

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是O(n^2)对于n<=50000过不了。那么这时候需要用斜率优化来降到至O(n)
首先转移的区间(i,k)
x=k-i-1+Sk-Si
令Ti=Si+i
那么对于区间(i,k)的代价Wi=(Tk-Ti-L-1)^2
先假设决策i优于决策j,那么dp(i)+Wi < dp(j)+Wj
设Ci=(Ti+L+1)
那么展开化简得到
((dp(i)+Ci^2)-(dp(j)+Cj^2)) / (2*(Ci-Cj)) < Tk
由于Tk=Sk+k,那么Tk具有单增性质,故把分子分母看成平面上两点坐标公式,可以得到:决策i所代表的点与决策j所代表的点之间连线的斜率 < Tk , 因此可以维护一个下凸壳,从斜率最小的点开始,找到的第一个斜率大于Tk的点就是对于阶段k的最优决策点。
最后更新凸壳的时候如果发现队尾的点与k点连线的斜率比队尾两点连线更小,说明这个大一些的线段一定不会对答案做出贡献,因此需要舍弃,每个点最多进队一次,所以总时间复杂度O(n)
需注意的是,C0=L+1,因为一开始进队的是0号点,这时候不管T0的值是多少,都应该加上L+1。说白了就是注意函数定义域。。。
另外,如果不具有决策单调性,需用平衡树来维护这个点集。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<string>
#include<iomanip>
#include<ctime>
#include<climits>
#include<cctype>
#include<algorithm>
#ifdef WIN32
#define AUTO "%I64d"
#else
#define AUTO "%lld"
#endif
using namespace std;
#define smax(x,tmp) x=max((x),(tmp))
#define smin(x,tmp) x=min((x),(tmp))
#define maxx(x1,x2,x3) max(max(x1,x2),x3)
#define minn(x1,x2,x3) min(min(x1,x2),x3)
typedef long long LL;
const LL INF=(1ll<<60);
const int maxn = 50005;
LL dp[maxn],s[maxn],c[maxn],t[maxn];
LL L,C;
int n;
void init()
{
    scanf("%d" AUTO ,&n,&L); C=L+1;
    for(int i=1;i<=n;i++)
    {
        LL tmp;
        scanf(AUTO,&tmp);
        s[i]=s[i-1]+tmp;
    }
    for(int i=0;i<=n;i++) t[i]=s[i]+i,c[i]=t[i]+L+1; // !!! make sure the c[] starts at 0 !!!! coz que[1]=0 and j is made 0 !!!!!!!!!!!!
}

double slope(int j,int i)
{
    double dy=(dp[i]+c[i]*c[i])-(dp[j]+c[j]*c[j]);
    double dx=(c[i]<<1)-(c[j]<<1);
    return dy/dx;
}
int que[maxn];
int head,tail;
LL dynamic()
{
    memset(dp,0,sizeof(dp));
    head=1; tail=0;
    que[++tail]=0; // line to start
    for(int k=1;k<=n;k++)
    {
        while(head<tail && slope(que[head],que[head+1])<=s[k]+k) head++;
        dp[k]=dp[que[head]]+(t[k]-t[que[head]]-L-1)*(t[k]-t[que[head]]-L-1);
        while(head<tail && slope(que[tail],k)<slope(que[tail-1],que[tail])) tail--;
        que[++tail]=k;
    }
    return dp[n];
}
int main()
{
#ifndef ONLINE_JUDGE
    freopen("toy.in","r",stdin);
    freopen("toy.out","w",stdout);
#endif
    init();
    LL ans=dynamic();
    printf(AUTO,ans);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值