• 李超线段树


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

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

    算法简述

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

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

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

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

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

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

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

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

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

    然后我们看新旧的交点 也就是 (displaystyle x= -frac{b_1-b_2}{k_1-k_2}) (注意 如果 (k_1=k_2) 要判断掉 , 直接退出就行了)

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

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

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

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

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

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

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

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

    代码实现

    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

  • 相关阅读:
    Reverse Integer
    First Bad Version
    Heaters
    Number of Segments in a String
    RPI学习--环境搭建_串口连接
    关键字const
    main函数参数的使用
    Redis 简单使用 and 连接池(python)
    南宁AI项目
    Go part 1 初探
  • 原文地址:https://www.cnblogs.com/zjp-shadow/p/9180340.html
Copyright © 2020-2023  润新知