斜率优化
斜率优化其实是一种思想,在 dp 的时候我们可能会遇到一类问题,这类问题依赖于之前的状态转移。因此多了一重枚举前一个元素的复杂度。而斜率优化是通过对一类特殊的最优化问题,尝试用决策点关于斜率的式子去除一些无用的决策,从而优化时间复杂度。
下面将给出几道习题来理解该算法。
[SDOI2016] 征途
Statement
求 \(\min\):\(m^2\times \sum\limits_{i=1}^{m}\frac{(a_i-S/m)^2}{m}\),其中 \(a_i\) 是每天走的距离,\(S=\sum a_i\)。
Analysis
展开上式
注意到 \(m\) 是常数,\(S^2\) 是常数,因此只需要最小化 \(\sum\limits_{i=1}^{m}a_i^2\) 即可。
有个非常朴素的 dp:设 \(f_{i,j}\) 表示走 \(i\) 天,走了 \(j\) 段路的最小值,那么
我们发现 \(f_i\) 的转移只和 \(f_{i-1}\) 有关,因此用滚动数组节约空间,因此可以令 \(g = f_{i - 1}\)。
时间复杂度 \(\mathcal{O}(nm^2)\)。
记 \(s_i = \sum\limits^i_{} a_i\)
尝试进行优化,考虑对于两个决策点 \(x,y(x>y)\),若 \(x\) 的决策比 \(y\) 优,则 \(g_{x}+(s_i - s_x)^2 < g_{y} + (s_i - s_y)^2\),通过拆项移项将已知量和未知量分开可得
假设 \(y_i = g_i + s_i ^ 2, x_i = s_i\),那么上述不等式可以看作是 \((x_i,y_i)\) 和 \((x_j,y_j)\) 两点连线的斜率小于 \(2s_i\)。
像这种可以将不等式拆成两点之间斜率和一条已知斜率直线进行比较的式子可以使用斜率优化。
接下来考虑斜率优化的工作流程(图来源于 oi-wiki)
考虑我们的决策必须经过某个点,而直线的斜率已经固定,因此可以用这根直线去切某个下凸包上的点。显然,相切这个点最优。一个点要能与这跟直线相切,满足 与前一个点的连线斜率 小于 已知直线的斜率,与后一个点的连线斜率 大于 已知直线的斜率。由于在凸包上,斜率是单调递增的,因此可以用单调队列维护。
这里给出单调队列部分代码。
for (int j = 1; j <= m; ++j) {
cur ^= 1;
memset(f[cur], 0, sizeof cur);
int l = 1, r = 0;
q[++r] = 0;
for (int i = 1; i <= n; ++i) {
while (l < r && slope(q[l + 1], q[l]) < 2 * s[i]) ++l;
f[cur][i] = f[cur ^ 1][q[l]] + sqr(s[i] - s[q[l]]);
while (l < r && slope(q[r], q[r - 1]) > slope(q[r], i)) --r;
q[++r] = i;
}
}
[NOI2019] 回家路线
笔者调了一天愣是没发现自己哪里写错了于是愤然重构代码居然一遍过,以下内容是笔者在精神不太正常的时候写下的。
听说这道题还有 \(\mathcal O(mt)\) 做法?
考虑朴素 dp,假设 \(f_i\) 表示坐第 \(i\) 辆列车时的最小烦躁值,可以得出一个 \(m^2\) 的 dp 式子。
在考虑到由于站点的转移限制,远跑不满 \(m^2\)。虽然可以构造数据卡掉但是好像没造?
暴力拆开式子移项:
因为 \(Ap_i^2 - Bp_i - C\) 是一定值,所以左侧的值决定决策点是否更优。
假设现在有两点 \(x,y(x>y)\),如果 \(x\) 比 \(y\) 更优,则:
标准式子,维护单调队列即可。
因为笔者根本不想调代码,因此不贴代码了。

浙公网安备 33010602011771号