标签: 斜率优化

[HNOI2008]玩具装箱 题解 & DP斜率优化原理与实现

[HNOI2008]玩具装箱 题解 & DP斜率优化原理与实现

题目地址:洛谷:【P3195】[HNOI2008]玩具装箱TOY – 洛谷、BZOJ:问题 1010. — [HNOI2008]玩具装箱toy

题目描述

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。但他希望费用最小.

输入输出格式

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

输出格式:
输出最小费用

输入输出样例

输入样例#1:

5 4
3
4
2
1
4

输出样例#1:

1

题解 & 斜率优化介绍

斜率优化是什么

斜率优化是一类利用决策单调性来优化DP转移的优化方式。因为其原理中一部分形似斜率,故名为斜率优化。

以本题为例,斜率优化怎么用

注意:以下推导为斜率优化的感性证明,严格的数学证明请参见其他资料。
首先我们很容易能想到的DP转移方程如下
dp[i] = \min\{ dp[j] + (sum[i] - sum[j] + i - j - 1 - L)^2 \}
我们令f[i] = sum[i] - i, C = L + 1,有
dp[i] = \min\{ dp[j] + (f[i] - f[j] - C)^2 \}
如果j是一个决策点,我们去掉min标记,展开平方,得到下式
dp[i] = dp[j] + f[i]^2 - 2f[i](f[j] + C) + (f[j] + C)^2
将与i无关的项放在等号右侧,而其他移至等号左侧
dp[i] + 2f[i](f[j] + C) - f[i]^2 = dp[j] + (f[j] + C)^2
在这个式子里,我们可以把2f[i]看成直线的斜率,那么就是一个斜率为2f[i]且过点 (f[j] + C, dp[j] + (f[j] + C)^2) 的一条直线,其纵截距为dp[i] - f[i]^2。显然我们想让这个值最小,我们使用类似线性规划的思路,知道当这个值最小的时候决策点应该在一个下凸壳上,就可以用单调队列维护这个凸包。
对于决策点j和k,规定k > j,如果k比j的决策更优,在这个题里面就是
dp[k] + (f[i] - f[k] - C)^2 < dp[j] + (f[i] - f[j] - C)^2
移项,我们会得到
dp[k] - dp[j] + (f[k] - C)^2 - (f[j] - C)^2 - 2f[i](f[k] - f[j]) > 0

\frac{dp[k] - dp[j] + (f[k] - C)^2 - (f[j] - C)^2}{2(f[k] - f[j])} > f[i]
这就是最终我们来判断单调队列中哪些点需要弹出的依据,其式子也形似斜率(实际上就是前面我们求出来的直线的斜率)。我们总是保持单调队列队首的决策点对于当前最优,而插入是把不如当前点优的点删去。因为f[i]是单调递增的,单调队列的维护也有了依据。
每一次转移用单调队列队首的点去转移即可。

代码

// Code by KSkun, 2018/3
#include <cstdio>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}

inline int readint() {
    register int res = 0, neg = 1;
    char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 50005;

int n, c, l, r, q[MAXN];
LL f[MAXN], dp[MAXN];

inline double sqr(LL x) {
    return x * x;
}

inline double slope(int x, int y) {
    return (dp[y] - dp[x] + sqr(f[y] + c) - sqr(f[x] + c)) / (2.0 * (f[y] - f[x]));
}

int main() {
    n = readint();
    c = readint() + 1;
    for(int i = 1; i <= n; i++) {
        f[i] = f[i - 1] + readint();
    }
    for(int i = 1; i <= n; i++) {
        f[i] += i;
    }
    l = 1;
    r = 0;
    q[++r] = 0;
    for(int i = 1; i <= n; i++) {
        while(l < r && slope(q[l], q[l + 1]) <= f[i]) l++;
        dp[i] = dp[q[l]] + sqr(f[i] - f[q[l]] - c);
        while(l < r && slope(q[r], i) < slope(q[r - 1], q[r])) r--;
        q[++r] = i;
    }
    printf("%lld", dp[n]);
    return 0;
}