首页 > 其他分享 >李超线段树 学习笔记

李超线段树 学习笔记

时间:2023-06-25 22:57:45浏览次数:49  
标签:tmp tr 线段 tree 李超 笔记 calc

李超线段树 学习笔记

今天模拟赛用到了李超线段树(但是本蒟蒻费了半天劲搞了个斜率优化拿到了 60pts 的好成绩 /kk),所以学习一下李超线段树刻不容缓(学会了我貌似也切不来那道题 qwq)。

引入

初中和高中我们都做过函数题吧,是不是有时候给你两根甚至几根直线,然后问你某个点的最值?当然,直线数很少的时候我们很容易做,甚至可以直接画图来看。现在我们要用计算机来解决这个问题,但是计算机可不会看图……怎么办呢?李超线段树出来力!

李超线段树是巨佬李超发明的一种数据结构,能在 \(\log_n\) ( \(n\) 为定义域大小,当然也可以通过动态开点来搞掉,不过都是后话了)的复杂度下求出几个一次函数在某一横坐标下,纵坐标的最大值。

思想

好吧李超线段树的思想其实也不是很复杂,简而言之就是,我们在每个节点(也就是区间)上,维护一条优势线段,其中优势线段的“优势”体现在区间中点处,也就是它在中点处能取到该点所有直线的最大纵坐标。

我们结合图来理解一下:

pCNHp5D.png

上图中,红色线段就是一条优势线段。

然后我们来考虑一下如何插入——

对于一个区间 \([l, r]\),我们要插入线段(直线) \(a\),分如下几种情况:

  1. 该区间没有线段,那么直接覆盖即可;
  2. 该区间已经有线段,但是 \(a\) 的左右端点都比原线段高,那么新线段就是优势线段,也是直接覆盖即可;
  3. 该区间已经有线段,而 \(a\) 的左右端点都比原线段低,那么就不作修改;
  4. 该区间已经有线段,而 \(a\) 与原线段 \(b\) 在区间内有交点,我们就讨论交点的位置:
    • 如果交点在区间中点左侧(如上图),我们令中点处值较大的一条线段为优势线段。由于交点在中点左侧,所以在右半个区间中,优势线段仍然不会改变,所以不用修改;由于我们不能确定左侧区间内优势线段是谁,所以我们将非优势线段传递下去,修改左侧即可。
    • 交点在区间中点右侧同理,只不过要继续修改的变成区间右侧。

那我们如何查询呢?

对于每个点的询问,我们必须要把包含该点的所有区间询问一次,取最大值。为什么呢?考虑这样一个问题:我们传递修改的过程中,只是把非优势线段传递下去,但是优势线段是无法下放的。也就是说,我们每个区间只是维护了一个相对优势的线段,来保证答案可能从中产生,而最终答案必须是从上往下都算一遍的。

例题

[洛谷 P4254 Blue Mary 开公司](P4254 [JSOI2008] Blue Mary 开公司 )

板子题,也没有啥细节,正好借这个题把板子总结一下。

代码:

#include<bits/stdc++.h>
#define ls tr<<1
#define rs tr<<1 | 1
#define LD long double
using namespace std;
const int N = 1e5+100, D = 5e4+100;


struct Line{
    double k, b;//y = kx+b;
    int l, r;//注意这里的l,r并不是记录区间的左右端点,而是记录线段的左右端点,这道题的线段都是直线,但是某些题需要对线段进行处理。
    int flag;//标记区间有无线段。
}tree[N<<2];

int n, tot;
double calc(int x, Line tmp){
    return tmp.k*x+tmp.b;
}//计算某个点的纵坐标值。
int cross(Line a, Line b){
    return (b.b-a.b)/(a.k-b.k);
}//计算中点横坐标。

void build(int tr, int L, int R){
    tree[tr] = {0, 0, 1, 50000, 0};//预处理。
    if(L == R) return;
    int mid = (L+R)>>1;
    build(ls, L, mid);
    build(rs, mid+1, R);
}

void modify(int tr, int L, int R, Line tmp){
    if(tmp.l<=L && R<=tmp.r){//首先判断线段是否包含区间[L, R]。
        if(!tree[tr].flag) tree[tr] = tmp, tree[tr].flag = 1;//情况1,直接覆盖。
        else if(calc(L, tmp)>calc(L, tree[tr])&&calc(R, tmp)>calc(R, tree[tr])) tree[tr] = tmp;//情况2,覆盖。
        else if(calc(L, tmp)>calc(L, tree[tr]) || calc(R, tmp)>calc(R, tree[tr])){//情况4
            int mid = (L+R)>>1;
            if(calc(mid, tmp)>calc(mid, tree[tr])){
                swap(tree[tr], tmp);
            }
            if(cross(tmp, tree[tr])<=mid) modify(ls, L, mid, tmp);
            else modify(rs, mid+1, R, tmp);
        }
    }else{//这一线段并不能代表区间内的情况,故要传递下去。
        int mid = (L+R)>>1;
        if(tmp.l<=mid) modify(ls, L, mid, tmp);
        if(mid<tmp.r) modify(rs, mid+1, R, tmp);
    }
}

double query(int tr, int L, int R, int x){//每走到一个区间就取一遍,求最大值。
    if(L == R) return calc(x, tree[tr]);
    int mid = (L+R)>>1;
    double ans = calc(x, tree[tr]);
    if(x <= mid) return max(ans, query(ls, L, mid, x));
    else return max(ans, query(rs, mid+1, R, x));
}
char op[10];
int main(){
    scanf("%d", &n);
    build(1, 1, 50000);
    while(n--){
        scanf("%s", op);
        if(op[0]=='Q'){
            int day;
            scanf("%d", &day);
            int ans = ((int)query(1, 1, 50000, day))/100;
            printf("%d\n", ans);
        } else{
            Line tmp;
            scanf("%lf%lf", &tmp.b, &tmp.k);
            tmp.l = 1, tmp.r = 50000;//这道题只对直线进行处理,所以左右端点可以看作定义域左右端点。
            tmp.b-=tmp.k;
            modify(1, 1, 50000, tmp);
        }
    }
    return 0;
}

标签:tmp,tr,线段,tree,李超,笔记,calc
From: https://www.cnblogs.com/frostwood/p/17504168.html

相关文章

  • 「学习笔记」vector
    本文并不是vector的入门教程。定义std::vector是封装动态数组的顺序容器。vector通常占用多于静态数组的空间,因为要分配更多内存以管理将来的增长。如果元素数量已知,可以使用reserve()函数提前分配内存。操作函数由于vector大家比较熟悉了,这里给大家带来一些其他......
  • C语言学习笔记
    斐波那契定义:斐波那契数列是一个数列,其中每个数字是前两个数字之和,起始于0和1。数列的定义如下:F(0)=0F(1)=1F(n)=F(n-1)+F(n-2)(对于n>1)换句话说,斐波那契数列的第n个数字是前两个数字之和,而前两个数字分别是0和1。数列的前几个数字如下所示:0,1,1,......
  • celery笔记七之周期/定时任务及crontab定义
    本文首发于公众号:Hunter后端原文链接:celery笔记七之周期/定时任务及crontab定义periodictask,即为周期,或者定时任务,比如说每天晚上零点零分需要运行一遍某个函数,或者每隔半小时运行一遍该函数,都是这种任务的范畴。在第一篇笔记的时候我们就介绍过celery的组件构成,其中有一......
  • 烧写文件系统——韦东山嵌入式Linux学习笔记11
    原文:https://blog.csdn.net/longintchar/article/details/71319513本文实验环境:1.windows7(64bit)2.JZ2440(V2)使用u-boot烧写文件系统,一般有两种方法。1.通过USB下载功能2.通过TFTP功能通过USB下载功能烧写文件系统这种方法比较简单。操作步骤:(1)连接板子和PC(串口+USB)(2)进入u-......
  • [数据结构]Segment tree(线段树)
    Segmenttree(线段树)1.线段树的结构和思想线段树基本结构:简单操作:1.单点修改:时间复杂度O(logn),因为线段树高是O(logn)的,然后会修改这个点到根的路径上的点权,所以是O(logn)的。2.区间查询(比如:最小值)实现:#include<bits/stdc++.h>usingnamespacestd;typedeflong......
  • 图论 学习笔记(省选+)
    网络流最大流问题(MaximumFlowProblem)有向有权图给定起点s和终点t预期:求出从s到t的最大流ps.有些“管道”达不到其最大容量朴素的匹配算法(NaiveAlgorithm):未必能找到最大流,其结果往往比最优解差一点,但是其他更好的算法都基于此算法构建一个和原图(OriginalGraph)相同......
  • 线性规划学习笔记
    线性规划学习笔记1 线性规划定义定义1.1已知一组实数\(a_1,a_2,\cdots,a_n\),以及一组变量\(x_1,x_2,\cdots,x_n\),在这些变量的一个线性函数定义为\(f(x_1,x_2,\cdots,x_n)=\sum_{i=1}\limits^na_ix_i\)。等式\(f(x_1,x_2,\cdots,x_n)=b\),不等式\(f(x_1,x_2,\cdots......
  • opencv学习笔记(十二)
    harris角点检测:#角点检测importcv2importnumpyasnp"""cv2.cornerHarris()img:数据类型为float32bolckSize:角点检测中指定区域的大小ksize:Sobel求导中使用的窗口大小,一般为3K:取值参数为[0.04,0.06]"""img=cv2.imread('C:/Users/hellou/Deskt......
  • P5JS学习笔记
    //启动方法,自动执行functionsetup(){createCanvas(400,400);}//绘画执行方法,自动执行,按设定好的帧数绘制functiondraw(){background(25);ellipse(50,50,80,80);//画圆//鼠标按下事件if(mouseIsPressed){//改变背景色fill(0);}else{fill(255......
  • C# Dapper和DapperExtensions笔记
    一、DapperDapper是一个简单的.NET对象映射器,在速度方面具有"KingofMicroORM"的头衔,几乎与使用原始的ADO.NET数据读取器一样快。ORM是一个对象关系映射器,它负责数据库和编程语言之间的映射。Dapper通过扩展IDbConnection提供一些有用的扩展方法去查询您的数据库。1.安装Dapp......