slope trick
slope trick
概述
在 \(dp\) 过程中,可以维护凸函数的方法,要求 \(dp\) 值呈凸函数且其斜率均为整数。
具体来说,是记录凸函数斜率的变化值,即在什么位置斜率\(\plusmn 1\),例如凸函数 \(f(x) = |x|\),它由一条斜率为 \(-1\) 和 一条斜率为 \(1\) 的射线在 \(0\)点处相连,那么在零点处斜率加了 \(2\),所以可以用 \([0, 0]\) 来表示这个函数。
例题
1.【CF713C】Sonya and Problem Wihtout a Legend
严格递增可以通过减 \(i\) 变为不严格。
对于这题,我们容易想到 \(dp\),设 \(f_{i,j}\) 表示填了 \(1\) 到 \(i\) 位,第 \(i\) 位填 \(j\) 的最小操作次数,容易得到转移
设 \(g_{i,j} = \min_{k\le j}f_{i,k}\),可以归纳证明 \(f, g\) 均为凸函数。
我们可以考虑维护 \(g\) 的凸函数,那它一定是形如左边一段弯的接一条斜率为 \(0\) 的直线。
那么每一转移就相当于加上 一个凸函数\(|a_i - x|\),再做前缀\(min\)。
设当前函数最低点的横坐标为 \(x\),就有两种情况:
当 \(x \le a_i\) 时,我们相当于将 \(a_i\) 左侧的线段斜率减一,那么只需要多加入一个转折点 \(a_i\),即可。
当 \(x > a_i\) 时,这时我们是将 \(a_i\) 左侧的斜率减一,右侧加一,所以我们应该加入两个 \(a_i\),而最小值加上了\(x - a_i\),所以答案也加\(x - a_i\),因为我们要做一次前缀\(min\),所以要删去一个 \(x\)。
这些操作都可以用堆维护,时间复杂度为\(O(n \log n)\)
Code
#include<bits/stdc++.h>
#define LL long long
#define eb emplace_back
#define IN inline
using namespace std;
int n;
IN int read() {
int t = 0,res = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) t |= (ch == '-');
for (; isdigit(ch); ch = getchar()) res = (res << 3) + (res << 1) + (ch ^ 48);
return t ? -res : res;
}
priority_queue<int> Q;
int main() {
n = read(); LL ans = 0;
for (int i = 1; i <= n; i++) {
int z = read() - i; Q.push(z);
if (Q.top() > z) ans += (LL)Q.top() - z, Q.pop(), Q.push(z);
}
printf("%lld\n", ans);
}
2. [ABC217H] Snuketoon
容易想到 \(dp\),设 \(f_{i,j}\) 表示经历了前 \(i\) 个事件,处于 \(j\) 受到的最小伤害,容易得到转移
后面部分为凸函数,可以归纳出 \(f\) 也为凸函数。
可以用两个堆来维护这个下凸包,一个维护左凸壳,一个维护右凸壳。
那么对于\(min\)转移,相当于将凸壳向两边移动了\(T_i - T_{i - 1}\)。
而加入的凸函数只需像 例 \(1\) 一样判断即可。
Code
#include<bits/stdc++.h>
#define LL long long
#define eb emplace_back
#define IN inline
using namespace std;
int n;
IN int read() {
int t = 0,res = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) t |= (ch == '-');
for (; isdigit(ch); ch = getchar()) res = (res << 3) + (res << 1) + (ch ^ 48);
return t ? -res : res;
}
priority_queue<LL> Ql;
priority_queue<LL, vector<LL>, greater<LL> > Qr;
int main() {
n = read(); LL tagl = 0, tagr = 0, ans = 0;
for (int i = 1; i <= n + 1; i++) Ql.push(0), Qr.push(0);
for (int i = 1, pt = 0; i <= n; i++) {
int t = read(), d = read(); LL X = read();
LL dt = t - pt;
tagl -= dt, tagr += dt;
if (d == 0) {
LL u = Qr.top() + tagr;
if (X > u) ans += X - u, Ql.push(u - tagl),Qr.pop(), Qr.push(X - tagr);
else Ql.push(X - tagl);
}
else {
LL u = Ql.top() + tagl;
if (X < u) ans += u - X, Qr.push(u - tagr), Ql.pop(), Ql.push(X - tagl);
else Qr.push(X - tagr);
}
pt = t;
}
printf("%lld\n", ans);
}