【学习笔记】DP 优化
目录
- ds 优化
- 单调队列优化
- 斜率优化
- 决策单调性优化
1. ds 优化
考虑枚举右端点 \(i\),求出 \(\sum\limits_{j=1}^if(j,i)\)。
假设我们维护了一个序列,\(j\) 位置存的是 \([j,i]\) 的答案。
先预处理出 \(lst_i\),表示 \(i\) 往左数第一个等于 \(a_i\) 的数的下标。
如果右端点从 \(i-1\) 变成 \(i\),发现对于所有 \(j~(lst_i+1<j\le i)\),它们的答案都要加 \(1\)。而对于 \(j~(j\le lst_i)\),由于 \(a_i\) 已经在 \(lst_i\) 上出现过,答案不变。
本题还要求出平方和,用线段树维护即可。
枚举建几个基站,建 \(t\) 个基站的答案可以从 \(t-1\) 转移过来。
用 \(f_i\) 表示在第 \(i\) 给村庄建一个基站,已经考虑了 \([1,i-1]\) 内的村庄的补偿,最少需要多少钱。
如果我们要求 \(f_i\),维护一个线段树,\(j\) 位置表示上一个基站在 \(j\) 村庄时(即从 \(j\) 转移),\(f_i\) 的值。
预处理 \(l_i,r_i\) 表示坐标在 \([d_i-s_i,d_i+s_i]\) 内的最左边和最右边的村庄。
\(f_i\) 是很好求的,只需要求个最小值。考虑如何更新这个序列。
如果有一个村庄 \(k\),\(r_k=i\),那么当我们求 \(f_{i^\prime}~(i^\prime>i)\) 时,如果从 \(j~(j<l_k)\) 转移,那么覆盖不到 \(k\),需要支付 \(w_k\)。
用 vector 或前向星,在 \(i\) 位置存所有 \(r_k=i\) 的 \(k\)。每次求出 \(f_i\) 时,枚举这里面的 \(k\),在 \([1,l_k-1]\) 上加上 \(w_k\)。
求出所有 \(f\) 后,重建线段树,把 \(f_i\) 放到 \(i\) 位置上,用于求建 \(t+1\) 个基站的答案。
练习题
2. 单调队列
求 \(f_i=\min\limits_{d_i\le j\le i}\{a_i\}\),其中 \(d_i\) 单调不减。
显然可以 st 表 \(O(n\log n)\),但有个 \(d_i\) 单调不减的性质没用上。
考虑维护一个队列,求 \(f_i\) 时,里面从小到大存了所有在 \([1,i-1]\) 内的下标。
但这里面有很多元素是没有用的。
\(\forall j,k\in[1,i-1],j<k\)
-
若 \(a_j\ge a_k\),考虑任意一个能被 \(j\) 更新到的 \(i~(i>k)\)(即 \(d_i\le j\)),必有 \(d_i\le j<k\) 即 \(k\) 也能更新 \(i\)。而 \(a_j\ge a_k\),\(j\) 并不优于 \(k\),没必要存在于队列中。
-
若 \(j<d_i\),\(j\) 无法更新 \(i\),而随着 \(i\) 增大 \(d_i\) 不会减小,即 \(d_i\) 再也没有用了。
综上,这个队列应时刻满足:
- 下标递增
- \(a_j\) 递增
- 队首 \(\ge d_i\)
求 \(f_i\) 时只需取出队首,因为它已经是最小的了。然后从队尾删除直到队尾的值 \(<a_i\),插入 \(i\)。
容易发现每个元素最多进、出各一次,所以时间复杂度 \(O(n)\)。
练习题
3. 斜率优化
对 \(c\) 做前缀和。设 \(f_i\) 表示以第 \(i\) 个玩具作为一个容器的最后一个玩具,前 \(i\) 个玩具的最小费用。
\(f_i=\min\limits_{1\le j<i}\{f_j+(j-i-1+c_i-c_j-L)^2\}\)
若令 \(L\) 加 \(1\),再令 \(c_k\) 加上 \(k\),方程变成 \(f_i=\min\limits_{1\le j<i}\{f_j+(c_i-c_j-L)^2\}\)
但它并不能用单调队列,因为展开后同时含有 \(c_i\times c_j\) 和 \(c_j^2\)。
考虑计算 \(f_i\) 时,选择 \(j,k~(j<k)\) 作为转移,如果 \(j\) 优于 \(k\)
\(f_j+(c_i-c_j-L)^2<f_k+(c_i-c_k-L)^2\)
\(f_j+c_j^2-2c_ic_j-2c_jL<f_k+c_k^2-2c_ic_k-2c_kL\)
\((f_j+c_j^2-2c_jL)-(f_k+c_k^2-2c_kL)<2c_i(c_j-c_k)\)
\(\dfrac{(f_j+c_j^2-2c_jL)-(f_k+c_k^2-2c_kL)}{c_j-c_k}>2c_i\)
对于第 \(t\) 个决策,在平面直角坐标系内找一点 \(P_t(c_t,f_t+c_t^2-2c_tL)\)。用 \(k_{j,k}\) 表示 \(P_j\) 和 \(P_k\) 连成线段的斜率,令\(K_i=2c_i\)。那么对于 \(i\),\(j\) 优于 \(k\) 当且仅当 \(k_{j,k}>K_i\)
仿照单调队列,我们维护一个队列,求 \(f_i\) 时,里面从小到大存了所有在 \([1,i-1]\) 内的下标。
这里面有很多点是没有用的。
- \(\forall x_1,x_2,x_1<x_2,k_{x_1,x_2}\le K_i\),此时 \(x1\) 不优于 \(x_2\)。因为 \(c_i\) 单调递增,\(K_i\) 也单调递增,对于 \(i^\prime~(i^\prime>i)\),\(x1\) 仍不优于 \(x_2\)。所以 \(x_1\) 是没用的。
- \(\forall x_1,x_2,x_3,x_1<x_2<x_3,k_{x_1,x_2}\ge k_{x_2,x_3}\),对于任意的 \(i\),\(k_{x_2,x_3}>K_i\),则 \(x_2\) 优于 \(x_3\)。而此时必有 \(k_{x_1,x_2}>K_i\),即 \(x_1\) 优于 \(x_2\)。所以 \(x_2\) 是没用的。
所以我们要维护一个斜率递增的下凸壳,每次看队首和队首的后一个元素的斜率,若 \(\le K_i\) 弹出队首。不断重复直到斜率大于 \(K_i\),然后从队首转移到 \(i\)。不断弹出队尾直到队尾的前一个元素和队尾的斜率小于队尾和 \(i\) 的斜率。然后在队尾插入 \(i\)。
有些题目会出现不存在斜率的情况(即两点横坐标相同),此时要特判并返回 \(\infty\) 或 \(-\infty\)(视具体情况而定)。
刚刚我们解决了 \(c_j\) (即决策点横坐标)单调递增,\(K_i\) 也递增的情况。
但有些题目决策点横坐标单调递增,但 \(K_i\) 不一定。此时刚才的第一条性质不一定满足,我们就不能从队首删点。此时我们就要在队列里二分,找到第一个点,满足它和下一个元素的斜率大于(或小于,视题目而定)\(K_i\),并把它作为决策点。
有些题目甚至决策点横坐标和 \(K_i\) 都不单调,此时就要用平衡树或 CDQ 分治。