slope trick

发布时间 2023-08-24 20:02:44作者: RiverSheep

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\) 的最小操作次数,容易得到转移

\[f_{i,j} = \min_{k \le j} f_{i - 1,k} + |a_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_{i,j} = \min_{k = j - (T_i - T_{i - 1})}^{j+(T_i - T_{i-1})}f_{i-1,k} + \max(0, X_i - j) or \max{0,j - X_i} \]

后面部分为凸函数,可以归纳出 \(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);
}