斜率优化 BZOJ 1010 HNOI 2008玩具装箱 详细解析。

惯于离经叛道中 体会心安理得 亦于按部就班中 痛感乏善可陈

题面如下:
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
这道题首先我们可以写出最简单的动态规划方程。
我们用F[i]表示装入前i个物品的最优值,所以状态转移方程就是
dp[i] = min{dp[j] + (i-j-1+sum[i]-sum[j]-L)^2}
最朴素的是枚举min括号里的j找到最小值,但这样是n^2会T,所以采用斜率优化。
然后我们进行化简把一些东西抛到min括号外面去
dp[i] = min{dp[j] + (j+sum[j])^2-2(i+sum[i]-L-1)(j+sum[j])}+(i+sum[i]-L-1)^2
然后我们对min括号里面的东西进行分析。
所以我们把不与i(不与i相乘)相关的东西写成一个函数up(i) =
dp[i] + (i+sum[i])^2+2(L+1)(i+sum[i])
然后我们令 j < k , (j和k就是你在朴素枚举min括号里面的最小值的东西)
把我们刚才min里面的东西分别带成j和k令他们的j算出的值小于k算出的值。化简为up(j)-up(k)<=2(i+sum[i])(j+sum[j]-k-sum[k])
把左边的除过去得(up(j)-up(k))/(j+sum[j]-k-sum[k]) >= 2(i+sum[i])(注意这里右边小于0除过去变号)

这里算出来的值有这几个含义:
1、如果满足这个不等式则j必定比k优,取j不取k。
2、我们把(up【j】,j+sum【j】)看成坐标轴上的一个点,那么左边的式子其实是j点和k点的斜率,因为是大于符号,且右边的东西单调递增,我们用向下凸的一个图形来保存。
第一条也就是我们取最新加入的且满足不等式的点是最优的(从小到大加入点),那么我们用单调队列的思想保存点,所以我们应保存一个单调递增的斜率所以如果队首的点不满足不等式我们就踢掉它直到找到一个点满足,所以应该用下凸(其实具体为什么用下凸我还有点迷,平时做题也就一个上凸一个下凸编程的时候写两个看哪一个对就好,只用改大小于符号)
所以直接上代码吧:

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
#define N 50005
#define dnt long long
using namespace std;
int n, head = 1, tail = 1, q[N];
dnt L, sum[N], dp[N];
dnt up( int x ) {
    return dp[x]+(x+sum[x])*(x+sum[x])+2*(L+1)*(x+sum[x]);
}
dnt down( int x ) {
    return x+sum[x];
}
dnt getdp( int i, int j ) {
    return dp[j]+(i-j-1+sum[i]-sum[j]-L)*(i-j-1+sum[i]-sum[j]-L);
}
int main() {
    scanf( "%d%I64d", &n, &L );
    for(int i=1;i<=n;scanf("%I64d",&sum[i]),sum[i]+=sum[i-1],i++);
    for ( int i = 1; i <= n; i++ ) {
        while (head<tail&&(up(q[head])-up(q[head+1]))>=(2*(i+sum[i])*(down(q[head])-down(q[head+1]))))
        head++;
        int k = q[head];
        dp[i]=getdp(i,k);
        while (head<tail&&(up(i)-up(q[tail]))*(down(q[tail])-down(q[tail-1]))<=(up(q[tail])-up(q[tail-1]))*(down(i)-down(q[tail])))
        tail--;
        q[++tail] = i;
    }
    cout << dp[n];
    return 0;
}

1010 Accepted 2268 kb 336 ms C++/Edit 971 B 2017-05-19 08:46:51
附单调队列详解PPT:链接: http://pan.baidu.com/s/1i5FWCVr 密码: ry27

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值