李超线段树

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

区间 \(\operatorname{max}\) 是李超线段树的一类经典应用,现在给定若干条直线 \(y_i=k_ix+b_i\),问在 \(x=x_i\) 的函数最大值。

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

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

  • \(k_i=k_j\)

    此时只需要比较 \(b_i\)\(b_j\) 即可。

  • \(k_i\not=k_j\)

    不妨设 \(k_i>k_j\),此时两线段有一交点 \(x_d\),且 \(x<x_d\) 时,\(y_i<y_j\)\(x\ge x_d\) 时,\(y_i>y_j\)。这提示我们可以采取分治的思想。

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

查询时将当前区间,左子区间,右子区间三个区间的最优线段在 \(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 @ 2023-05-30 15:32  MisterRabbit  阅读(151)  评论(0)    收藏  举报