斜率优化DP 笔记

· 算法

CF 816的E要用到,计划是先学个大概再来写这题

先看的 OI-wiki 上内容

「HNOI2008」 玩具装箱

LOJ上的数据过水,就挂了洛谷的链接

维护一个前缀和 preipre_i,考虑 N2N^2 的DP

fi=min⁡j<i{(fj+i−(j+1)+prei−prej−L)2}f_i=\min_{j<i}\{(f_j+i-(j+1)+pre_i-pre_j-L)^2\}

令 si=prei+is_i=pre_i+i,则有

fi=min⁡j<i{fj+(si−sj−(L+1))2}f_i=\min_{j<i}\{f_j+(s_i-s_j-(L+1))^2\}

令 L′=L+1L'=L+1,展开移项得到

fi−(si−L′)2=min⁡j<i{fj+sj2+2sj(L′−si)}f_i-(s_i-L')^2=\min_{j<i}\{f_j+s_j^2+2s_j(L'-s_i)\}

考虑一次函数的截剧式 y=kx+by=kx+b,移项得 b=y−kxb=y-kx,与 jj 有关的信息表示为 yy 的形式,与 ijij 有关的信息表示为 kxkx,要最小化的信息表示为 bb,即

xj=sjyj=fj+sj2ki=2(si−L′)b=fi−(si−L′)2\begin{align*} &x_j=s_j\\ &y_j=f_j+s_j^2\\ &k_i=2(s_i-L')\\ &b=f_i-(s_i-L')^2 \end{align*}

转移方程就变成

b=min⁡j<i{yj−kixj}b = \min_{j<i}\{y_j-k_ix_j\}

那就变成求截距最小值

(图片缺失:slope1.svg)

不难发现备选的点集一定在下凸包上,不用从前 i−1i-1 个点转移过来

用单调队列维护 K(a,b)K(a,b),下凸包上相邻两点 a,ba,b 间的斜率,这个肯定是递增的,这样就可以维护一个指针 ee,每次移动找到 K(e−1,e)<ki<K(e,e+1)K(e-1,e)<k_i<K(e,e+1),ee 就是当前的选项

发现就是第一个 K(e,e+1)>2(L′−si)K(e,e+1)>2(L'-s_i) 的地方,那么之前小于 2(L′−si)2(L'-s_i) 的选项都可以不要

更新完了之后,若 K(back−1,back)>K(back,i)K(back-1,back)>K(back,i),那么就弹出队尾

这样肯定是把最下面一圈的外轮廓维护出来的

最后找到之后移项一下就OK了,只用到初中数学知识、、

#include <bits/stdc++.h>
using namespace std;
const int N = 50010;
int n, l;
int c[N];
long long pre[N], s[N], f[N];

inline long long a(int i) { return s[i] - l - 1; }
inline long long x(int i) { return s[i]; }
inline long long y(int i) { return f[i] + s[i] * s[i]; }
inline double K(int a, int b) { return (y(a) - y(b)) / (double)(x(a) - x(b)); }

int main() {
  cin >> n >> l;
  for (int i = 1; i <= n; ++i) {
    cin >> c[i];
    pre[i] = pre[i - 1] + c[i];
    s[i] = pre[i] + i;
  }
  static int q[N], hd = 0, bk = 0;
  q[bk++] = 0;
  for (int i = 1; i <= n; ++i) {
    while (hd + 1 < bk && K(q[hd], q[hd + 1]) < 2.0 * a(i))
      hd++;
    f[i] = f[q[hd]] + (s[q[hd]] - a(i)) * (s[q[hd]] - a(i));
    while (hd < bk - 1 && K(q[bk - 2], q[bk - 1]) > K(i, q[bk - 1]))
      bk--;
    q[bk++] = i;
  }
  cout << f[n] << '\n';
  return 0;
}

斜优DP大概就是把转移式子写出来之后,发现可以拆项成一个一次函数的表现形式

然后配合二分、CDQ的操作快速筛选


现在来尝试拿下CF816的E..

CF816 E. Long Way Home

给定 nn 个点 mm 条边的图,每组点对 (u,v)(u,v) 还有一条特殊路径,长 (u2−v2)(u^2-v^2),至多选择
kk 条特殊路径,问 1 走到 nn 的最短路径

考虑已经取了 kk 条特殊边的最短路,记为 dkd_k,那么怎么求 dk+1d_{k+1} 呢,考虑推导一个 fi=min⁡1≤j≤n{dk[j]+(i−j)2}f_i=\min\limits_{1\leq j \leq n}\{d_k[j]+(i-j)^2\},即在 dkd_k 基础上,每个点以一条特殊边结尾的最短路,在 fif_i 的基础上再跑一边 Dijkstra,就得到至多用 k+1k+1 条特殊边的最短路

那么现在的做法就是 O(k(mlogn+n2))O(k(mlogn+n^2)) 的,关键就是在于
fif_i 更新的速度

考虑

fi=min⁡1≤j≤n{d[j]+(i−j)2}  ⟺  fi−i2=min⁡{d[j]+j2−2ij}f_i=\min_{1\leq j\leq n}\{d[j]+(i-j)^2\} \iff f_i-i^2=\min\{d[j]+j^2-2ij\}

那么由 b=y−kxb=y-kx 得到

b=fi−i2y=d[j]+j2k=2ix=j\begin{align*} &b=f_i-i^2\\ &y=d[j]+j^2\\ &k=2i\\ &x=j \end{align*}

因为 k=2i>0k=2i>0 并且逐渐增大,那么还是用单调队列维护上凸包就行了

因为可以从任意的点转移过来,并且 jj 作为 xx 也是递增的,因此把所有的斜率插入再搞,然后单点移动一下,就是 O(n)O(n) 的,总体就是
O(k(mlogn+n))O(k(mlogn+n)) 的了

单调栈还是单调队列?无所谓了..

好像还有个决策单调性+分治做法,可以看wc的视频

大概就是先跑一边最短路,求出d之后,做k次的 [斜优求f,然后以f为基础跑一遍最短路求出新d]

然后找到对应转移点 vv 之后有

b=y−kx  ⟺  fi−i2=d[v]+v2−2ij  ⟺  fi=d[v]+(i−v)2b=y-kx \iff f_i -i^2=d[v]+v^2-2ij \iff f_i=d[v]+(i-v)^2

submission

好了... 去打游戏了..