Slope trick 学习笔记
Slope trick 的定义
Slope trick 是一种通过分析 DP 函数在转移时的斜率变化来优化转移的技巧。通常来说,被维护的函数图像是离散的凸函数,Slope trick 会维护函数的斜率或者斜率的差分。
维护凸函数主要有以下几个优点:
-
方便维护形如
的操作(等会的例题会讲怎么维护)。 -
方便维护加法操作,两个凸函数相加仍然是凸函数。
-
方便维护
加法卷积操作(形如 )。 -
维护差分后方便快速找极值。
题目引入
这道经典题有多种做法和理解方式,这里仅介绍从 Slope trick 角度出发的思路。
首先令
- (
) (买一份) - (
) - (
)
可以发现
感性认知一下:
证明:(a)构建费用流模型;(b)归纳法(
假设
首先,第
我们把
根据上面的理论,对于左边进行了转移的那一部分,我们有
我们用堆来维护斜率,最终答案就是最左上角的高度。因为右下角的高度是
#include <bits/stdc++.h> #define int long long using namespace std; int n, ans; priority_queue<int, vector<int>, greater<int>> que; inline int read(int &x) { char ch = x = 0; int m = 1; while (ch < '0' || ch > '9') { ch = getchar(); if (ch == '-') m *= -1; } while (ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + ch - 48; ch = getchar(); } x *= m; return x; } signed main() { read(n); int p; for (int i = 1; i <= n; i++) { read(p); if (!que.empty() && que.top() < p) { ans += p - que.top(); que.pop(); que.push(p); } que.push(p); } printf("%lld", ans); return 0; }
总结一下:当我们确定 DP 图像是凸的时,我们可以利用凸函数的性质(如:斜率单调,找极值等)快速维护状态转移对应的图像变化。再知道图像其中一个位置的值后,就可以通过斜率把所有 DP 值恢复过来。这一类题目往往要先发现凸性,然后要通过画图来感受转移带来的图像变化,选择合适的维护方式。
P3642 [APIO2016] 烟火表演
同上处理,令
-
(
)当 时,这时直接修改 肯定比修改下面子树更优,从式子上看就是 越大越好,所以直接让 ,得到 。 -
(
)当 时,从式子知道,因为 在 时的变化率不小于 的变化率,所以 越接近 越好,因此让 ,得到 。 -
(
)当 时,因为 时 在上升, 时 比 变化的更快,因此 在下降,所以我们让 ,于是有 。 -
(
)当 时,令 时明显是最小的,此时 。
看一看这些操作在斜率数组上是怎样的(这里一定要画一下图!这样会更容易理解!):操作
因为要实现函数的加法,直接维护斜率不好维护,所以这里我们维护斜率的拐点(或者说差分),让一个拐点表示斜率在这个位置加一(所以拐点可重)。令
#include <bits/stdc++.h> #define int long long #define N 600005 using namespace std; int n, m, fa[N], l[N], num[N], rt[N], ans, tot; struct node { int l, r, val, dis; } t[N]; inline int read(int &x) { char ch = x = 0; int m = 1; while (ch < '0' || ch > '9') { ch = getchar(); if (ch == '-') m *= -1; } while (ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + ch - 48; ch = getchar(); } x *= m; return x; } int merge(int x, int y) { if (!x || !y) return x + y; if (t[x].val < t[y].val) swap(x, y); t[x].r = merge(t[x].r, y); if (t[t[x].l].dis < t[t[x].r].dis) swap(t[x].l, t[x].r); t[x].dis = t[t[x].r].dis + 1; return x; } inline int pop(int x) { return merge(t[x].l, t[x].r); } signed main() { read(n), read(m); for (int i = 2; i <= n + m; i++) { read(fa[i]), read(l[i]); num[fa[i]]++; ans += l[i]; } for (int i = n + m; i > 1; i--) { int L = 0, R = 0; if (i <= n) { while (--num[i]) rt[i] = pop(rt[i]); R = t[rt[i]].val, rt[i] = pop(rt[i]); L = t[rt[i]].val, rt[i] = pop(rt[i]); } t[++tot].val = L + l[i]; t[++tot].val = R + l[i]; rt[i] = merge(rt[i], merge(tot, tot - 1)); rt[fa[i]] = merge(rt[fa[i]], rt[i]); } while (num[1]--) rt[1] = pop(rt[1]); while (rt[1]) ans -= t[rt[1]].val, rt[1] = pop(rt[1]); printf("%lld", ans); return 0; }
感谢围观!如有错误请大佬们指出!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!