李超线段树

这个利用了线段树标记永久化的思想 , 支持查询很多条直线 y=kx+b (线段)在 x=k 的最值 .

常常可以在一些最优化问题中 优化时间复杂度 , 增强程序效率 .

算法简述

假设我们当前维护最大值 (最小值同理) .

用线段树维护每一个区间的一个 优势线段 (暴露在最上面的线段 , 也就是不会被别的线段在这个区间完全盖住)

对于网上那些说暴露最多的 , 他们程序都似乎不能体现 , 故个人理解是这样 .

可以证明 , 对于 x=k 在这些直线上的最大值 , 肯定是所有包含这个点的所有区间 优势线段 对应 y 的最大值 .

暴露在最上面线段 是个抽象化的过程 , 我们可以把这个看成在 [l,r] 区间中 f(mid=l+r2) 的最大的那条直线 .

我们插入的时候讨论四种情况 qwq

  1. 这个区间本来没有线段 , 直接放在这里就行了 .
  2. 新的线段完全被旧的线段盖住 , 这种情况直接退出就行了 .
  3. 新的线段把旧的线段完全盖住 , 直接修改然后退出就行了 .
  4. 在这个区间中有交点 , 先改 , 然后把劣势的放入交点的那一侧 .

这个说起来容易 , 但实现起来有点细节 .

我们先比较 f(mid) 大小 , 如果新的大 , 那我们先交换 , 也就是说 把当前较为优势的先放在此处 , 劣势的拿下来等待安排 .

然后我们看新旧的交点 也就是 x=b1b2k1k2 (注意 如果 k1=k2 要判断掉 , 直接退出就行了)

x 如果不在此段区间内 , 那么意味着优势的在这个区间都优于劣势 , 那么直接退出 , 否则 把当前劣势的下方入标点那侧继续递归处理 .

有时候判断交点不行 , 会错掉 , 其实最好的是判断斜率 .

因为有时候交点刚好在 mid 处时候 , 你可能会存在瞎走的情况 .

这时候斜率能帮助你判断接下来应该向哪里走 , 也就是它接下来哪里会最优 .

然后查询的话 , 在线段树上一直向下走 , 直到走入端点所处的区间 , 然后一路把存在的优势线段的 f(x)max .

分析一波时间复杂度qwq ...

插入的时候每个线段最多被分成 O(logn) 个区间 , 然后继续下放也需要 O(logn) 的复杂度 , 插入的时候复杂度就是 O(log2n) . (如果插入直线的话就是 O(logn) 的复杂度)

然后查询的话和普通单点查询的复杂度是一样的 O(logn) .

代码实现

const int N = 5e4 + 1e3; struct Line { int l, r, id; double k, b; Line (int xl = 0, int xr = 0, int yl = 0, int yr = 0, int id = 0) { this -> id = id; l = xl, r = xr; if (xl != xr) k = 1.0 * (yr - yl) / (xr - xl), b = yl - k * xl; else k = .0, b = max(yl, yr); } double func(int x) { return k * x + b; } } ; const double eps = 1e-8; inline int Sgn(double x) { return (x > eps) - (x < -eps); } inline bool Cmp(Line a, Line b, int x) { if (!a.id) return true; int dir = Sgn(a.func(x) - b.func(x)); return (dir != 0) ? dir < 0 : a.id < b.id; } #define lson o << 1, l, mid #define rson o << 1 | 1, mid + 1, r struct Chao_Segment_Tree { Line Adv[N << 2]; void Down(int o, int l, int r, Line up) { int mid = (l + r) >> 1; if (Cmp(Adv[o], up, mid)) swap(Adv[o], up); if (l == r || Sgn(Adv[o].k - up.k) == 0) return ; double x = (Adv[o].b - up.b) / (up.k - Adv[o].k); if (x < l || x > r) return ; if (x <= mid) Down(lson, up); else Down(rson, up); } void Insert(int o, int l, int r, int ul, int ur, Line up) { if (ul <= l && r <= ur) { Down(o, l, r, up); return ; } int mid = (l + r) >> 1; if (ul <= mid) Insert(lson, ul, ur, up); if (ur > mid) Insert(rson, ul, ur, up); } Line Query(int o, int l, int r, int qp) { if (l == r) return Adv[o]; int mid = (l + r) >> 1; Line tmp; tmp = (qp <= mid ? Query(lson, qp) : Query(rson, qp)); return Cmp(Adv[o], tmp, qp) ? tmp : Adv[o]; } } T;

例题讲解

  1. BZOJ 3165: [HEOI 2013]Segment

    要求在平面直角坐标系下维护两个操作:

    1. 在平面上加入一条线段。记第 i 条被插入的线段的标号为 i
    2. 给定一个数 k ,询问与直线 x=k 相交的线段中,交点最靠上的线段的编号。

    这个题就是模板题啦 qwq

    但是网上好多都像我这种 , 把线段基本上视作一条直线 , 从顶至底更新的时候 , 没有判断当前更新的线段 是否覆盖了 x=k 这个范围 就导致答案失真... (ps : 这个一拍就错啦) 但是官方数据好像很神奇 竟然过啦 qwq

    我太菜啦 , 懒得改啦 , 注意下这个问题就行了 ....

  2. Codeforces Round #463 F. Escape Through Leaf

    又来骗访问啦 qwq 一道好题 2333


__EOF__

本文作者zjp_shadow
本文链接https://www.cnblogs.com/zjp-shadow/p/9180340.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zjp_shadow  阅读(792)  评论(3编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示