首页 > 其他分享 >李超线段树

李超线段树

时间:2024-04-09 15:02:40浏览次数:18  
标签:return int 线段 李超 calc id op

李超线段树一般用来解决线段交集凸包问题:

  • 加入一个一次函数,定义域为 \([l,r]\)
  • 给定 \(k\),求定义域包含 \(k\) 的所有一次函数中,在 \(x=k\) 处取值最大的那个,如果有多个函数取值相同,选编号最小的

看到区间修改,我们按照线段树解决区间问题的常见方法,给每个节点一个懒标记,每个节点 \(i\) 的懒标记都是一条线段,记为 \(l_i\),表示要用 \(l_i\) 更新该节点所表示的整个区间

考虑现在要插入一条新的线段 \(v\)

第一步要按照线段树的常规方法找到被 \(v\) 完整覆盖的区间

第二步是修改区间内的懒标记,具体如何如何修改?

考虑一个区间的懒标记原来的值为 \(u\),若原来没有值,则直接为 \(v\)

image.png

不妨假设 \(u\) 在区间中点处比 \(v\) 更大的情况(若比 \(v\) 小,交换 \(u,v\) 即可)

  1. 若在左端点处 \(v\) 更优,那么 \(u\) 和 \(v\) 必然在左儿子内相交,递归更新左儿子
  2. 若在右端点处 \(v\) 更优,那么 \(u\) 和 \(v\) 必然在右儿子内相交,递归更新右儿子
  3. 若左右端点处 \(u\) 都更优,那么 \(v\) 不可能成为答案,则不需要下传

除了这两种情况之外,还有一种情况是 \(u\) 和 \(v\) 正好相较于中点,在程序实现时可以归入中点不如 \(u\) 的情况,依旧会更新一边

考虑时间复杂度,线段树最多把区间分成 \(\log{L}\) 个小区间,每个小区间更新的时间复杂度时 \(log{L}\) ,总时间复杂度为 \(\log^2{L}\)

代码实现

#include <bits/stdc++.h>
using namespace std;
const int mod1 = 39989, mod2 = 1e9;
const double eps = 1e-9;
typedef pair<double, int> pdi;

int cmp(double x, double y) {
    if (fabs(x - y) < eps) return 0;
    if (x < y) return -1;
    return 1;
}

struct Line {
    int id;
    double k, b;
    Line() {k = 0, b = 0; id = 0;}
    Line(int x_0, int y_0, int x_1, int y_1, int id) {
        this->id = id;
        if (x_0 == y_0) k = 0, b = max(y_0, y_1);
        else k = 1.0 * (y_1 - y_0) / (x_1 - x_0), b = y_0 - k * x_0;
    }
    double calc(int x) { return k * x + b;}
};

bool operator < (const pdi &a, const pdi &b) {
    if (cmp(a.first, b.first) == 0) return a.second > b.second;
    return a.first < b.first;
}

pdi max (const pdi &a, const pdi &b) {
    return a < b ? b : a;
}
int max_x = 0;
struct Segment_Tree {
    int n;
    vector<Line> t;
    Segment_Tree (int n) {
        this->n = n;
        t.resize(4 * n);
    }
    void upd_line (int x, int l, int r, Line v) {
        max_x = max(max_x, x);
        auto &u = t[x];
        int mid = (l + r) / 2;
        int op_mid = cmp(v.calc(mid), u.calc(mid));
        if (op_mid == 1 || (op_mid == 0 && v.id < u.id)) swap(u, v);
        if (l == r) return;
        int op_le = cmp(v.calc(l), u.calc(l)), op_ri = cmp(v.calc(r), u.calc(r));
        if (op_le == 1 || (op_le == 0 && v.id < u.id)) 
            upd_line (x << 1, l, mid, v);
        if (op_ri == 1 || (op_ri == 0 && v.id < u.id))
            upd_line (x << 1 | 1, mid + 1, r, v);
    }

    void upd_pos (int x, int l, int r, int ql, int qr, Line v) {
        max_x = max(max_x, x);
        if (ql <= l && r <= qr) {
            upd_line (x, l, r, v);
            return;
        }
        int mid = (l + r) / 2;
        if (ql <= mid) upd_pos (x << 1, l, mid, ql, qr, v);
        if (qr > mid) upd_pos (x << 1 | 1, mid + 1, r, ql, qr, v);
    }

    pdi query (int x, int l, int r, int pos) {
        max_x = max(max_x, x);
        if (l == r) return pdi(t[x].calc(pos), t[x].id);
        int mid = (l + r) / 2;
        pdi res = pdi(t[x].calc(pos), t[x].id);
        if (pos <= mid) res = max(res, query(x << 1, l, mid, pos));
        else res = max(res, query(x << 1 | 1, mid + 1, r, pos));
        return res;
    }
};

int main() {
    freopen ("P4097.in", "r", stdin);
    freopen ("P4097.out", "w", stdout);
    int n; cin >> n;
    int lastans = 0, cnt = 0;
    Segment_Tree T (mod1 + 1);
    for (int i = 1; i <= n; i++) {
        int op; cin >> op;
        if (op == 0) {
            int x; cin >> x;
            x = (x + lastans - 1) % mod1 + 1;
            // cout << x << endl;
            lastans = T.query(1, 1, mod1, x).second;
            cout << lastans << endl;
        }
        else {
            int x_0, x_1, y_0, y_1; cin >> x_0 >> y_0 >> x_1 >> y_1;
            x_0 = (x_0 + lastans - 1) % mod1 + 1, x_1 = (x_1 + lastans - 1) % mod1 + 1;
            y_0 = (y_0 + lastans - 1) % mod2 + 1, y_1 = (y_1 + lastans - 1) % mod2 + 1;
            // cout << x_0 << " " << y_0 << " " << x_1 << " " << y_1 << endl;
            if (x_0 > x_1) swap(x_0, x_1), swap(y_0, y_1);
            T.upd_pos(1, 1, mod1, x_0, x_1, Line(x_0, y_0, x_1, y_1, ++cnt));
        }
    }
    // cout << max_x << endl;
    return 0;
}

标签:return,int,线段,李超,calc,id,op
From: https://www.cnblogs.com/martian148/p/18123989

相关文章

  • [DS 小计] 线段树合并
    引入现在你有很多棵二叉树。二叉树的节点总和是\(n\)。现在,你要把它们合并。怎么做呢?实际上,写的好是可以\(O(n)\)完成的。前置题目1给出\(2\)棵二叉树,合并两棵二叉树。怎么做呢?很容易的暴力,遍历每个点,合并即可。合并我们进行以下分类讨论:如果现在\(u\),\(v......
  • [DS 小计] 线段树分治
    很简单的一个小trick。就看能不能想出来。思考我们学过CDQ分治。CDQ分治的思想是把时间切割,询问截断点后面的问题,预处理截断点前面的贡献。这是把问题和修改放在一起的。那么,假设修改很难撤销怎么办?这时候就可以用线段树分治做到只增不删。有点类似回滚莫队。算法直......
  • 每日一题 第六十五期 洛谷 线段树1
    【模板】线段树1题目描述如题,已知一个数列,你需要进行下面两种操作:将某区间每一个数加上kkk。求出某区间每一个数的和。输入格式第一行包含两个整数......
  • [DS 小计] 李超线段树
    前言大家好啊,今天讲的是LCT(李超Tree)(bushi错了错了。这两不是一个东西。概念李超树能干什么。插入一条直线/线段单点查询当前点的峰值(最大最小均可)你会说:OI中有什么是和斜率相关的吗?有,那就是斜率优化。关于斜率优化可以看这个。你会说:你说的对,静态的dp......
  • 牛客小白月赛90----->D.小A的线段(easy version)
    1,思路:因为只有10个线段所以直接暴力枚举所有方案,看满足条件的方案有多少个,我这里用的是二进制枚举(dfs也可以),时间复杂度是:1024*1e5=1e8,这个时间复杂度是可以接受的。2.代码:#include<iostream>#include<algorithm>#include<cmath>#include<cstring>usingnamespacestd;......
  • 线段树模板
    #include<iostream>#include<map>#include<algorithm>#include<math.h>usingnamespacestd;/*3372*/typedeflonglongll;constintN=1e5+10;lla[N]={0};lltree[N<<2]={0};lltag[N<<2]={0};llls(......
  • #离线,线段树#SP1557 GSS2 - Can you answer these queries II
    题目给定大小为\(n\)的序列,\(q\)次询问,求最大子段和,相同的数只算一次。选出的子段可以为空。分析数字不重复很难做,考虑离线,询问区间按右端点排序枚举区间右端点,不重复就相当于给\([pre+1,i]\)为开头的区间后添加\(a_i\)那么相当于维护以\(j\)为开头的最大子段和,以......
  • #线段树,模拟费用流#CF280D k-Maximum Subsequence Sum
    题目给定一个大小为\(n\)的序列,要求支持单点修改和查询区间内至多\(k\)个不交子区间之和的最大值(可以不取)分析考虑源点向每个点、每个点向汇点流流量1费用0的边,每个点向右边的点流流量1费用\(a_i\)的边,流量最大为\(k\),这样构建出一个费用流的模型。很显然,退流相当于给区......
  • 基本线段树以及相关例题
    1.线段树的概念线段树是一种二叉树,也就是对于一个线段,我们会用一个二叉树来表示。这个其实就是一个线段树,我们会将其每次从中间分开,其左孩子就是左边的集合的和,其右孩子就是右边集合的和;我们可以用一个结构体tree去表示线段树的结点,tree.L代表线段树左边界,tree.R代表线段......
  • [算法学习笔记] 线段树
    前言线段树可以维护一系列区间问题。包括但不限于区间加,区间乘,区间最值等。本文主要介绍最基础的区间修改区间查询。你可以认为是模板线段树的解析。本文将持续更新。例题给定一个数列,需要支持如下两种操作:\(1\)\(x\)\(y\)\(k\)将区间\([x,y]\)中每一个数加\(k\)......