给享乐找个理由吧 若是被人嘲笑 回以嘲笑就好了

发布时间 2023-10-08 10:36:02作者: Z1qqurat

简介

李超线段树是一种能维护一些线段/直线极值的数据结构,具体能维护 \(x = k\) 时所有线段中最大/最小的 \(y\) 值,支持动态插入线段,单点的全局查询。一般可以用于维护不保证斜率单调性的凸包(比如做斜率优化 dp);在树上问题还能支持线段树合并,具体就是在节点处插入线段,其他的和普通线段树没有区别;如果想要维护区间信息可以用线段树/树状数组+离线做扫描线嵌套,也可以做到 \(\log ^2 n\)

经典应用:P4097 动态插入线段,查询单点极值

省流:每次插入线段 \(y = kx + b\),其定义域 \(x \in [l, r]\);求当前局面下 \(x = k\) 时取到最大 \(y\) 值的线段,若有多条这样的线段,输出最早出现(编号最小的)那一条。

李超线段树自然是线段树的结构,也就是以 \(x\) 轴为下标划分的线段树。那代表区间 \(x \in [l, r]\) 的线段树节点 \(p\) 维护的是什么?设 \(mid = \frac {l + r}{2}\)\(p\) 维护的就是在 \(mid\) 处取到最大值的线段是哪一条。那怎么维护这个信息呢?我们现在考虑插入一条线段 \(y = kx + b\),其定义域 \(x \in [l, r]\),那找出所有被 \([l, r]\) 完全包含的线段树上区间,只有这些区间的中点处最优线段可能会改变,于是我们考虑对这样的一个区间 \(p \rightarrow [l, r]\) 进行更新:(这里我们定义在 \(x\) 处,线段 \(u\) 优于 \(v\)\(u\)\(x\) 的值大于 \(v\)\(x\) 的值,或者这两者值相同但是 \(u\) 编号比 \(v\) 小)

设现在要插入的线段为 \(u\),原先的最优线段为 \(v\)&v = tr[p] 是为了方便直接修改 \(tr_p\)

  • 如果 \(u\)\(mid = \frac {l + r}{2}\) 处优于 \(v\),那么将 \(u, v\) 互换,往下递归。记得这时候如果 \(l = r\) 直接 return ;

  • 考虑 \(p\) 的更改对两个儿子的影响,我们将目前的 \(u\)\(l, r\) 处的取值与 \(v\)\(l, r\) 处的取值比较:

    • 如果 \(u\)\(l\) 处优于 \(v\)\(l\) 处,说明 \(u, v\)\([l, mid]\) 肯定有交点,\(u\) 会对左区间的一些节点值产生影响,所以往左儿子递归。

    • 如果 \(u\)\(r\) 处优于 \(v\)\(r\) 处,同上。

    • 否则 \(u\) 就是无用的线段。

那我们怎么定位 \(y = kx + b, x \in [l, r]\) 完全覆盖的区间呢?像线段树一样就好了。对一个区间的插入是 \(\log V\) 的,因为 \(u, v\) 最多一个交点,所以最多往一边递归;每条线段只会完全覆盖 \(\log V\) 个区间。所以全局插入一条线段是 \(\mathcal {O} (\log ^ 2 V)\) 的。

查询怎么做?这里采取标记永久化的思想,查询 \(x = k\) 处的极值,那么从线段树根节点到 \([k, k]\) 的路径上经过的所有节点的最优线段都有可能成为最终答案,进行一个权值 & 编号的比较即可。其余和普通线段树无异。

代码实现有一些小细节:

  • 这题要求在 \(y\) 取到最大的基础上最小化线段的标号,所以要专门写一个 pair 以及自定义的比较器。

  • 因为是浮点数的计算所以类型会转化的运算中要开 double,然后判断大小的时候设一个精度误差 \(eps\)

  • 计算直线斜率的时候如果两端点 \(x_0 = x_1\),记得设 \(k = 0\),要不然直接除会除 \(0\) 报错。

  • 强制在线模数的问题。首先 \(x,y\) 的模数不同,其次取模后必须是整数,所以采取 +P 再 %P 的方式。

  • 线段树空间大小:值域的四倍。

贴一下 Code:

#include <bits/stdc++.h>
#define ll long long
#define db double
#define lc (p << 1)
#define rc (p << 1 | 1)
#define pdi pair<db, int>
#define mr make_pair
#define fi first
#define se second
using namespace std;
const int N = 1e5 + 5, M = 4e4 + 5, M1 = 39989, M2 = 1e9;
const db eps = 1e-9;
int n, m, lst;
struct Line{
    db k, b;
} a[N];

int newline(int x0, int y0, int x1, int y1) {
    m++;
    if(x0 == x1) a[m].k = 0, a[m].b = max(y0, y1);
    else a[m].k = (db)1.0 * (y0 - y1) / (x0 - x1), a[m].b = (db)y0 - (db)a[m].k * x0;
    return m;
}

db calc(int u, db x) {
    return a[u].k * x + a[u].b;
}

int cmp(db x, db y) {
    if(x - y > eps) return 1;
    else if(y - x > eps) return -1;
    else return 0;
}

namespace LiChaoSegT{
    int tr[N << 2];
    void upd(int p, int l, int r, int u) {
        int &v = tr[p], mid = l + r >> 1;
        int bmid = cmp(calc(u, mid), calc(v, mid));
        if(bmid == 1 || (!bmid && u < v)) swap(u, v);
        if(l == r) return ;
        int bl = cmp(calc(u, l), calc(v, l)), br = cmp(calc(u, r), calc(v, r));
        if(bl == 1 || (!bl && u < v)) upd(lc, l, mid, u);
        if(br == 1 || (!br && u < v)) upd(rc, mid + 1, r, u);
        return ;
    }
    void modify(int p, int l, int r, int u, int x, int y) {
        if(x <= l && r <= y) return upd(p, l, r, u), void();
        int mid = (l + r) >> 1;
        if(x <= mid) modify(lc, l, mid, u, x, y);
        if(y > mid) modify(rc, mid + 1, r, u, x, y);
        return ;
    }
    void modify(int u, int x, int y) {
        modify(1, 1, M1, u, x, y);
    }
    pdi pmax(pdi x, pdi y) {
        int b = cmp(x.fi, y.fi);
        if(b == 1) return x;
        else if(b == -1) return y;
        return (x.se < y.se ? x : y);
    }
    pdi query(int p, int l, int r, int x) {
        if(x < l || x > r) return {0, 0};
        pdi ret = {calc(tr[p], x), tr[p]};
        if(l == r) return ret;
        int mid = l + r >> 1;
        if(x <= mid) return pmax(ret, query(lc, l, mid, x));
        else return pmax(ret, query(rc, mid + 1, r, x));
    }
    int query(int x) {
        return query(1, 1, M1, x).se;
    }
} using namespace LiChaoSegT;

int main() {
    cin >> n;
    for (int i = 1, o, x0, x1, y0, y1; i <= n; ++i) {
        cin >> o >> x0;
        if(o == 0) {
            x0 = (x0 + lst - 1 + M1) % M1 + 1;
            lst = query(x0); cout << lst << "\n";
        }
        else {
            cin >> y0 >> x1 >> y1;
            x0 = (x0 + lst - 1 + M1) % M1 + 1, x1 = (x1 + lst - 1 + M1) % M1 + 1;
            y0 = (y0 + lst - 1 + M2) % M2 + 1, y1 = (y1 + lst - 1 + M2) % M2 + 1;
            if(x0 > x1) swap(x0, x1), swap(y0, y1);
            int u = newline(x0, y0, x1, y1);
            modify(u, x0, x1);
        }
    }
    return 0;
}