目录

P3195 [HNOI2008]玩具装箱

目录

0.关于斜率优化DP

特点:看起来很nb,如果打错了式子会很难调(我不看题解调不出来)https://啧.tk/xyx https://啧.tk/qiang

1.这道题的做法

emmm……先看最简单版的DP式子 $ f[i] = \min { f[j] + ( pre_i - pre_j + i - j - 1 - L)^2 } (j < i) $ 有点复杂,那就来简化一下,设 $ S_i = pre_i + i $ $ L' = L + 1 $

这样,原式= $ f[i] = min{ f[j] + (S_i-S_j-l')^2 } $

展开得 $ f[i] - (S_i - L')^2 = 2S_j(L'-S_i) + f[j] + S_j^2 $

整理得 /images/toypack/toy.png

然后我们就得到了一个y = kx + b的函数图像 /images/toypack/toy1.svg

我们的目的是让 b 最小,所以考虑找一个点过图像使它最小。

如图,我们将这个斜率为 $ k_i $ 的直线从下往上平移,直到有一个点 $ (x_p,y_p) $ 在这条直线上,则有 $b_i=y_p-k_ix_p$ ,这时 $ b_i $ 取到最小值。算完 $ f_i $ ,我们就把 这个点加入点集中,以做为新的 DP 决策。那么,我们该如何维护点集?

容易发现,可能让 $ b_i $ 取到最小值的点一定在下凸壳上。因此在寻找 $ p $ 的时候我们不需要枚举所有 $ i - 1 $ 个点,只需要考虑凸包上的点。而在本题中 $ k_i $ 随 $ i $的增加而递增,因此我们可以单调队列维护凸包。 具体地,设 $ K(a,b) $ 表示过 $(x_a, y_a)$ 和 $(x_b,y_b)$ 的直线的斜率。考虑队列 $q_l,q_{l+1},\ldots,q_r$ ,维护的是下凸壳上的点。也就是说,对于 $l<i<r$,始终有 $K(q_{i-1},q_i) < K(q_i,q_{i+1})$ 成立。 我们维护一个指针 $e$ 来计算 $b_i$ 最小值。我们需要找到一个 $K(q_{e-1},q_e)\le k_i< K(q_e,q_{e+1})$ 的 $e$(特别地,当 $e=l$ 或者 $e=r$ 时要特别判断),这时就有 $p=q_e$,即 $q_e$ 是 $i$ 的最优决策点。由于 $k_i$ 是单调递减的,因此 $e$ 的移动次数是均摊 $O(1)$ 的。 在插入一个点 $(x_i,y_i)$ 时,我们要判断是否 $K(q_{r-1},q_r)<K(q_r,i)$,如果不等式不成立就将 $q_r$ 弹出,直到等式满足。然后将 $i$ 插入到 $q$ 队尾。

OIwiki上的解释(我解释不清了(逃)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <bits/stdc++.h>
using namespace std;
const long long maxn = 100010;

long long dp[maxn], pre[maxn], c[maxn], que[maxn];
long long n, l;
long long getS(long long id) { return pre[id] + id; }
long long getX(long long id) { return getS(id); }
long long getY(long long id) { return dp[id] + getS(id) * getS(id); }
long long getK(long long id) { return 2 * (getS(id) - l); }

long double getSL(long long i, long long j) {
  return (long double)(getY(j) - getY(i)) / (getX(j) - getX(i));
}

int main() {
  scanf("%lld%lld", &n, &l);
  l++;
  long long head, tail;
  head = tail = 1;
  for (long long i = 1; i <= n; i++) {
    scanf("%lld", &c[i]);
    pre[i] = pre[i - 1] + c[i];
  }
  for (long long i = 1; i <= n; i++) {
    while (head < tail &&
           getY(que[head + 1]) - getY(que[head]) -
                   (getX(que[head + 1]) - getX(que[head])) * getK(i) <
               0)
      head++;
    dp[i] = getY(que[head]) - getK(i) * getX(que[head]) +
            (getS(i) - l) * (getS(i) - l);
    while (head < tail && (getY(que[tail]) - getY(que[tail - 1])) *
                                  (getX(i) - getX(que[tail])) >=
                              (getY(i) - getY(que[tail])) *
                                  (getX(que[tail]) - getX(que[tail - 1])))
      tail--;
    que[++tail] = i;
  }
  cout << dp[n] << endl;
  return 0;
}