李超线段树
简介
李超线段树通常维护两个操作:
-
插入一个一次函数
-
查询直线
处的先前插入的函数最值
流程
插入
考虑因为插入的都是直线,所以函数在区间
李超线段树维护的叫做最优势线段,也就是线段树上区间
然后在此过程后,一定会有一条直线会被刷掉。由于先前所述的函数单调性,我们只需要比较剩下的直线的端点处的函数值是否比当前线段树上区间最优势线段在端点处的函数值便可以继续递归。
值得一提的是,如果插入的是线段,则可以按普通线段树区间修改一样划分成若干个线段树上的区间去插入。
查询
跟普通线段树的大差不差,具体的见代码。
例题
P4097 【模板】李超线段树 / [HEOI2013] Segment
要求插入线段和查询最值。
插入代码:
inline void insert(int &p, int L, int R, int x) {
if(! x) return ;
if(! p) p = ++ tot;
int y = id[p];
if(x > y) swap(x, y);
if(F(a[y], mid) > F(a[x], mid)) swap(x, y);
id[p] = x;
if(F(a[y], L) > F(a[x], L) || fabs(F(a[y], L) - F(a[x], L)) < eps) insert(lson, y);
if(F(a[y], R) > F(a[x], R) || fabs(F(a[y], R) - F(a[x], R)) < eps) insert(rson, y);
return ;
}
inline void add(int l, int r, int x, int &p, int L = 1, int R = INF) {
if(! p) p = ++ tot;
if(l <= L && R <= r) {
insert(p, L, R, x);
return ;
}
if(l <= mid) add(l, r, x, lson);
if(r > mid) add(l, r, x, rson);
return ;
}
查询代码:
inline int query(int k, int &p, int L = 1, int R = INF) {
if(L == R) return id[p];
int x = id[p], res = 0;
if(k <= mid) res = query(k, lson);
else res = query(k, rson);
if(x > res) swap(x, res);
if(F(a[res], k) > F(a[x], k)) swap(x, res);
return x;
}
P4655 [CEOI2017] Building Bridges
首先要拆除的柱子一定是连续的,因此我们想到可以用前缀和去维护这一部分。
设
答案:
转移:
考虑枚举断点并由断点 dp 值转移过来,则有:
注意第
这样时间复杂度是
假设当前考虑到的断点
将下标为
不妨将右边的
又因为左侧的
代码:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 5;
int n, x, cnt, h[N], f[N], dp[N], pre[N];
struct Line {
int k, b;
Line(int kk = 0, int bb = 0) {
k = kk, b = bb;
}
} a[N];
inline int F(Line l, int x) {
return l.k * x + l.b;
}
namespace Segment_Tree {
#define mid (L + R) >> 1
#define son p, L, R
#define lson ls[p], L, (L + R) >> 1
#define rson rs[p], ((L + R) >> 1) + 1, R
int tot, root, ls[N << 2], rs[N << 2], id[N << 2];
inline void insert(int x, int &p, int L = 0, int R = 1e6) {
if(! p) p = ++ tot;
int y = id[p];
if(F(a[y], mid) < F(a[x], mid)) swap(x, y);
id[p] = x;
if(F(a[y], L) < F(a[x], L)) insert(y, lson);
if(F(a[y], R) < F(a[x], R)) insert(y, rson);
return ;
}
inline int query(int x, int &p, int L = 0, int R = 1e6) {
if(L == R) return F(a[id[p]], x);
int res = F(a[id[p]], x);
if(x <= mid) res = min(res, query(x, lson));
else res = min(res, query(x, rson));
return res;
}
#undef mid
#undef son
#undef lson
#undef rson
}
using namespace Segment_Tree;
signed main() {
ios_base :: sync_with_stdio(NULL);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> n;
for(int i = 1 ; i <= n ; ++ i)
cin >> h[i];
for(int i = 1 ; i <= n ; ++ i)
cin >> x, pre[i] = pre[i - 1] + x;
a[0] = Line(0, 1e18);
a[++ cnt] = Line(-2 * h[1], h[1] * h[1] - pre[1]);
insert(cnt, root);
for(int i = 2 ; i <= n ; ++ i) {
// for(int j = 1 ; j <= i - 1 ; ++ j)
// f[i] = min(dp[i], f[j] + (h[i] - h[j]) * (h[i] - h[j]) + pre[i - 1] - pre[j]);
dp[i] = query(h[i], root) + h[i] * h[i] + pre[i - 1];
a[++ cnt] = Line(-2 * h[i], dp[i] + h[i] * h[i] - pre[i]);
insert(cnt, root);
}
cout << dp[n];
return 0;
}
- UB:更新一个
struct
类型的变量时一定要调用构造函数!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!