李超线段树

线段树是一类维护点的操作的数据结构,当我们处理线段问题时,同样可以将其归约到线段覆盖的一系列点上。这就是李超线段树的核心思想。

区间 max 是李超线段树的一类经典应用,现在给定若干条直线 yi=kix+bi,问在 x=xi 的函数最大值。

朴素的做法是求出 yi=kixi+bi,如果继续沿着这个想法优化比较困难,于是我们尝试将直线的贡献拆到点上。

假设对于一个区间,我们已经维护了每个点 max 的线段编号 i,现在加入一条线段 j,有以下几种可能:

  • ki=kj

    此时只需要比较 bibj 即可。

  • kikj

    不妨设 ki>kj,此时两线段有一交点 xd,且 x<xd 时,yi<yjxxd 时,yi>yj。这提示我们可以采取分治的思想。

    如果在某一半中,一条直线被另一条直线完全偏序,可以给这一半打上标记,递归另一侧。具体地,对于一段区间 [l,r],我们取得 i,j 两条直线在 mid 处的取值,如果 yj>yi,则认为 j 是这个区间内的最优直线,然后判断 lr 两点的取值决定分治哪边。正确性显然。

查询时将当前区间,左子区间,右子区间三个区间的最优线段在 x 的值比较即可。

值得注意的是,李超线段树也可以方便的解决线段的问题。因为在建线段树时,横坐标的左右边界已经被标定了,虽然说是直线,其实本质上也是线段,只要在对应的位置上插入即可。

贴一份典题代码:

#include <bits/stdc++.h>

using namespace std;

inline int read() {
    int x = 0, f = 0; char c = getchar();
    for (; !isdigit(c); c = getchar()) f |= c == '-';
    for (; isdigit(c); c = getchar()) x = x * 10 + (c & 15);
    return f ? -x : x;
}

const int N = 1e5 + 5, P1 = 39989, P2 = 1e9;
const double eps = 1e-5;
int n, rnk[N << 4];

struct node {
    double k, b;
} p[N];

int cnt;
inline void add(double x0, double y0, double x1, double y1) { 
    ++cnt;
    if (abs(x1 - x0) < eps) p[cnt].k = 0, p[cnt].b = max(y0, y1);
    else p[cnt].k = 1. * (y1 - y0) / (x1 - x0), p[cnt].b = y0 - p[cnt].k * x0;
}

double y(int id, int x) { return p[id].b + p[id].k * x; }

// cmp_double: 1 -> x > y, -1 -> x < y, 0 -> x = y
inline int cmp_double(double x, double y) {
    if (x - y > eps) return 1;
    if (x - y < -eps) return -1;
    return 0;
}

pair<double, int> max(pair<double, int> x, pair<double, int> y) {
    int c = cmp_double(x.first, y.first);
    if (c == 1) return x;
    if (c == -1) return y; 
    return x.second < y.second ? x : y;
}

pair<double, int> query(int p, int l, int r, int x) {
    if (r < x || l > x) return {0, 0};
    int mid = (l + r) >> 1;
    double res = y(rnk[p], x);
    if (l == r) return {res, rnk[p]};
    return max({res, rnk[p]}, max(query(p << 1, l, mid, x), query(p << 1 | 1, mid + 1, r, x)));
}

void modify(int p, int l, int r, int id) {
    int &bef = rnk[p], mid = (l + r) >> 1;
    if (cmp_double(y(id, mid), y(bef, mid)) == 1) swap(id, bef);
    // if (l == r) return;

    int bl = cmp_double(y(id, l), y(bef, l)), br = cmp_double(y(id, r), y(bef, r));
    if (bl == 1 || (!bl && id < bef)) modify(p << 1, l, mid, id);
    if (br == 1 || (!br && id < bef)) modify(p << 1 | 1, mid + 1, r, id);
}

void update(int p, int l, int r, int al, int ar, int id) {
    if (al <= l && r <= ar) {
        modify(p, l, r, id);
        return;
    }
    int mid = (l + r) >> 1;
    if (al <= mid) update(p << 1, l, mid, al, ar, id);
    if (mid < ar) update(p << 1 | 1, mid + 1, r, al, ar, id);
}

int main() {
    int n = read(), las = 0;
    while (n--) {
        int opt = read();
        if (opt == 0) {
            printf("%d\n", las = query(1, 1, P1, (read() + las - 1 + P1) % P1 + 1).second);
        }
        else {
            int x0 = (read() + las - 1 + P1) % P1 + 1, y0 = (read() + las - 1 + P2) % P2 + 1, 
                x1 = (read() + las - 1 + P1) % P1 + 1, y1 = (read() + las - 1 + P2) % P2 + 1;
            if (x0 > x1) swap(x0, x1), swap(y0, y1);
            add(x0, y0, x1, y1);
            update(1, 1, P1, x0, x1, cnt);
        }
    }
    return 0;
}

代码略显复杂的原因是这题还要求了编号最小,实际实现时一般不会做此要求。

posted @   MisterRabbit  阅读(82)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
点击右上角即可分享
微信分享提示