斜率优化
斜率优化
2 种方法
比如这个式子: (from [HNOI2008]玩具装箱TOY)
\[f[i] = \min\{ f[j] + (sum[i] + i - sum[j] - j - L - 1) ^ 2 \}
\]
大概有两种方法:
- 考虑 \(i, j\) 的关系, 将分别只和 \(i, j\) 有关的式子分开,
整理成为 \(y = kx + b\) 的形式, 要求其中 \(b\) 为和 \(f[i]\) 有关的式子, \(y\) 是和 \(f[j]\) 有关的式子, \(x\) 和 \(j\) 有关, \(k\) 和 \(i\) 有关
这样一个 \(j\) 就成了一个坐标 \((x, y)\), 本题中取 \(\min\), 就转化成了在和 \(i\) 有关的斜率 \(k\) 下取最小的 \(b\) 即截距 - 考虑 \(i\) 的两个子状态 \(j, k\), 他们谁更优, 这里假定 \(j > k\), \(j\) 比 \(k\) 更优的情况
然后将 \(j, k\) 分别带入 \(f[i]\) 的递推式中, 同样将仅与 \(j, k\) 有关的式子分开, 整理为 \(\frac{y_j - y_k}{x_j - x_k} < k\)
其中 \(y\) 是包含 \(f\) 的, \(x\) 不包含, \(k\) 则是仅与 \(i\) 有关, 取 \(\min\) 就用 \(<\), 反之亦然
以上都是基本的转化, 现在考虑两种方法分别如何求最优值
首先都是要画图, 然后就会发现两种方法是极其类似的,
- 假设有 3 个可以取的点组成 上凸折线, 由线性规划的方法容易得到, 用任何斜率取切, 中间那个上凸点都切不到, 会被另外两个点卡住
显然, 不管斜率如何, 都只需要留下下凸包的点 - 任然用这三个点, 首先左边两点的斜率 > 右边, 然后枚举一下现在的斜率与这两个的关系, 可以得到 3 中情况, 可以发现中间点都不用取
得到了同样的结论
于是, 对于每个 \(i\), 可以在 \([1, i - 1]\) 这些点组成的凸包上的点取值, 用 \(i\) 的斜率一个一个比过去即可,
例如本题中保留下凸包, 对于两种方法, 都是找到第一个大于当前斜率的点
不难想到可以用二分来查找
然而, 许多题都满足 \(i\) 的斜率有单调性, 比如这题, 两种方法中斜率都是 \(2(sum[i] + i)\), 其中两个变量都是单增的, 所以单增
于是, 凸包中也是斜率不断增的, 对于每个 \(i\) , 把从凸包起点开始不断删(即上面的暴力过程), 直到大于当前斜率, 显然这样复杂度是线性的
接下来用本题具体演示一下
第一种:
设 \(A[j] = (sum[j] + j + L + 1)\)
\[f[j] + A[j] ^ 2 = 2 \cdot (sum[i] + i) \cdot A[j] + f[i] - (sum[i] + i) ^ 2
\]
第二种:
设 \(A[j] = (sum[j] + j + L + 1)\)
\[\frac{(f[j] + A[j] ^ 2) - (f[k] + A[k] ^ 2)}{a[j] - a[k]} \le 2 \cdot (sum[i] + i)
\]
代码是一样的
int n;
const int MAXN = 5e4 + 10;
LL L, sum[MAXN], a[MAXN], f[MAXN];
struct Node
{
LL x, y;
} q[MAXN];
int h, t;
/*
0815
*/
int main()
{
n = in();
L = in();
for (int i = 1; i <= n; ++ i)
{
sum[i] = sum[i - 1] + in();
a[i] = (sum[i] + i + L + 1);
}
a[0] = L + 1;
f[0] = 0;
h = t = 1;
q[1].x = a[0], q[1].y = a[0] * a[0];
for (int i = 1; i <= n; ++ i)
{
while (h < t && (q[h + 1].y - q[h].y) <= 2 * (sum[i] + i) * (q[h + 1].x - q[h].x)) ++ h;
f[i] = q[h].y - 2 * (sum[i] + i) * q[h].x + (sum[i] + i) * (sum[i] + i);
Node now = (Node) { a[i], f[i] + a[i] * a[i] };
while (h < t && (now.y - q[t].y) * (q[t].x - q[t - 1].x) <= (q[t].y - q[t - 1].y) * (now.x - q[t].x)) -- t;
q[++ t] = now;
}
printf("%lld\n", f[n]);
return 0;
}