【题目描述】

P教授要去看奥运,但是他舍不下他的玩具,于是他决定把所有的玩具运到北京。

他使用自己的压缩器进行压缩,其可以将任意物品变成一堆,再放到一种特殊的一维容器中。

P教授有编号为1...N的N件玩具,第i件玩具经过压缩后变成一维长度为$ C_i $.

为了方便整理,P教授要求在一个一维容器中的玩具编号是连续的。

同时如果一个一维容器中有多个玩具,那么两件玩具之间要加入一个单位长度的填充物,形式地说如果将第$ i $件玩具到第$ j $个玩具放到一个容器中,那么容器的长度将为 $ x = j - i + \sum_{k = i}^j C_k $

制作容器的费用与容器的长度有关,根据教授研究,如果容器长度为$ x $,其制作费用为$ (X - L)^2 $.其中$ L $是一个常量。

P教授不关心容器的数目,他可以制作出任意长度的容器,甚至超过$ L $。但他希望费用最小.

【题目链接】

BZOJ 1010 玩具装箱Toy【HNOI 2008】

【普通DP】

斜率优化初探,我们首先来考虑一个普通DP。

设$f[i]$表示$[1, i]$的玩具装箱的最小代价,考虑将哪些玩具和$ i $放一起,设是将$ [j, i] $放在统一容器中,易得转移:

$$ f[i] = min\{ \ f[j - 1] + (i - j + s_i - s_{j - 1} - L)^2 \ \} \ j \in [1, i]$$

直接枚举$ j $转移,总复杂度是$O(n^2)$的。

那么新技能来啦,斜率优化!

【斜率?】

我们要分析决策之间的关系。

先来推一波式子,

记决策 $ j $的决策费用为$ F(j) $,即, $$ F(j) = f[j - 1] + (i - j + s_i - s_{j - 1} - L)^2 $$

我们将平方项展开,然后使$ i $和$ j $尽量分离——然而并不要展开后再分离,五项的完全平方一点都不好玩,所以可以先分离后展开,

记, $$ h(i) = i + s_i - L$$ $$ x(j) = s_{j - 1} + j$$ 则有, $$ F(j) = f[j - 1] + (h(i) - x(j))^2 $$ 展开得, $$ F(j) = f[j - 1] + x(j) ^ 2 + h(i) ^ 2 - 2h(i)x(j) $$ 大概就只能分离成这样子了, $ -2h(i)x(j) $一项无法分离。

那么考虑两个可行决策$ j_1, j_2 $孰优孰劣,不妨设$ j_1 < j_2 $,然后研究$j_2$优于$j_1$,即$ F(j_2) < F(j_1) $的充要条件。

那么我们有:

$$ f[j_2 - 1] + x(j_2) ^ 2 + h(i) ^ 2 - 2h(i)x(j_2) < f[j_1 - 1] + x(j_1) ^ 2 + h(i) ^ 2 - 2h(i)x(j_1) $$

整理,得,

$$ ( f[j_2 - 1] + x(j_2)^2 ) - ( f[j_1 - 1] + x(j_1)^2 ) < 2h(i)(x(j_2) - x(j_1)) $$

为简洁,设$y(i) = f[i - 1] + x(i)^2 $,则有,

$$ y(j_2) - y(j_1) < 2h(i)(x(j_2) - x(j_1))$$

我们知道,$x(j)$是单调增的,所以除过来,就有,

$$ \frac { y(j_2) - y(j_1) } {x(j_2) - x(j_1)} < 2h(i) $$

嗯,终于见到斜率了,斜率优化这个名字没骗人。

记斜率, $$ Slope(j_1, j_2) = \frac { y(j_2) - y(j_1) } {x(j_2) - x(j_1)} $$

就这样,我们得到了当$ j_1 < j_2 $时,$ j_2 $比$ j_1 $优的充要条件,也就是说:

$$ Slope(j_1, j_2) < 2h(i) \Leftrightarrow F(j_2) < F(j_1) $$

【优化?】

所以得到了斜率,可怎么来优化呢?

第一,以一个队列来维护可选决策,删除冗余决策,只储存有价值的决策,使得任意$j_k, j_{k + 1}$总有$ Slope(j_k, j_{k + 1}) > 2h(i) $,这样,最优决策总是在队首。

为什么不满足该条件的决策是冗余的呢?

简要证明,如果在计算过程中出现了决策$a, b$,使得$ Slope(a, b) < 2h(i) $,能说明些什么呢?

说明$ b $比$ a $更优,而且,由于$ 2h(i) $是单调增的,所以对于$ \forall i' > i $,总有$ Slope(a, b) < 2h(i) < 2h(i') $,也就是说,$ b $永远比$ a $优!故$ a $是冗余决策,可以删除。

证毕。

第二,可以维护相邻决策之间的斜率单调增。

简要证明,设依次有三个相邻决策$j_1, j_2, j_3$,如果$j_2$想要成为最优决策,那么必然要满足: $$ Slope(j_1, j_2) < 2h(i) \\ Slope(j_2, j_3) > 2h(i) $$ 那么有, $$ Slope(j_1, j_2) < Slope(j_2, j_3) $$ 也就是说$ j_2 $使得斜率单调增,这样的决策才有价值保留,若$j_2$使得决策单调减,则其必不可能成为最优决策,可以删除。

证毕。

说了这么长,其实核心代码很简单:

static int Q[MAXN];
int l = 0, r = -1;
for(int i = 1; i <= n; i++){
    while(l < r && slope(Q[r - 1], Q[r]) > slope(Q[r], i)) r--;
    Q[++r] = i;
    while(l < r && slope(Q[l], Q[l + 1]) < 2 * h(i)) l++;

    int j = Q[l];
    f[i] = f[j - 1] + sqr(j - i + c[i] - c[j - 1] - L);
}

(代码高亮莫名炸了不知所措QwQ,正在检查...)

时间复杂度$O(n)$,因为每个决策最多被删除一次。

【AC代码】

#include <cstdio>
#include <climits>
#include <algorithm>

const int MAXN = 50061;
typedef long long int64;
 
int n, L;
int64 c[MAXN];
 
inline int64 sqr(int64 x){
    return x * x;
}

int64 f[MAXN];
 
inline int64 x(int64 i){
    return i + c[i - 1];
}
 
inline int64 y(int64 i){
    return f[i - 1] + sqr(x(i));
}
 
inline double slope(int i, int j){
    return (double)(y(j) - y(i)) / (x(j) - x(i));
}
 
inline int64 h(int i){
    return i + c[i] - L;
}
 
inline void dp(){
    static int Q[MAXN];
    int l = 0, r = -1;
    for(int i = 1; i <= n; i++){
        while(l < r && slope(Q[r - 1], Q[r]) > slope(Q[r], i)) r--;
        Q[++r] = i;
        while(l < r && slope(Q[l], Q[l + 1]) < 2 * h(i)) l++;

        int j = Q[l];
        f[i] = f[j - 1] + sqr(j - i + c[i] - c[j - 1] - L);
    }
}
 
int main(){
    scanf("%d%d", &n, &L);
 
    c[0] = 0;
    for(int i = 1; i <= n; i++) scanf("%lld", c + i), c[i] += c[i - 1];
 
    dp();
 
    printf("%lld\n", f[n]);

    return 0;
}

就是这样啦。