• 李超线段树


    介绍

    李超线段树,是用来解决平面直角坐标系中直线或线段的集合在某一点 x x x处的最大值或最小值问题。

    在实现李超线段树的时候,打的标记是不用下传的,也就是标记永久化。在查询时,根节点到对应叶节点上的所有节点的贡献都要计算。

    线段树的区间对应的是平面直角坐标系的 x x x轴上的区间,对于每个区间,要维护的是在这个区间上较优的直线或线段。


    维护线段

    在线段树上的操作如下:

    • 修改操作:加入一条线段
    • 查询操作:查询所有包含 x = x 0 x=x_0 x=x0的线段在 x = x 0 x=x_0 x=x0时能取到的最大值

    下面来分类讨论一下每种情况。

    • 如果线段覆盖的这个区间与当前区间并不相交,则直接返回
    • 如果线段覆盖了这个区间的一部分,则递归对应的左右子区间继续处理
    • 如果线段覆盖了整个区间
      • 如果线段在区间两端点处的值都比原来区间大,则用当前线段替换原来的线段并返回
      • 如果线段在区间两端点处的值都比原来区间小,则直接返回
      • 否则,比较当前线段和原来线段在区间中点处的值,如果当前线段值更大,则将原来线段和当前线段交换;然后判断左右端点的值,如果当前线段在一边不占优势,则递归对应左右子区间

    修改时要先找到修改分配到 log ⁡ n \log n logn个线段树上的区间,这些区间需要 O ( log ⁡ n ) O(\log n) O(logn)的时间复杂度下传,所以单次修改的时间复杂度为 O ( log ⁡ 2 n ) O(\log^2 n) O(log2n)。同样地,查询也是 O ( log ⁡ 2 n ) O(\log^2 n) O(log2n)

    例题

    P4097 【模板】李超线段树 / [HEOI2013] Segment

    模板题,按上面说的做即可。时间复杂度为 O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n)

    code

    #include
    using namespace std;
    const double eps=1e-8;
    const long long mod1=39989,mod2=1000000001;
    int n,s1=0,cnt=0,rt=0,ans=0;
    struct node{
    	double k,b;
    }s[100005];
    struct tree{
    	int lc,rc,c;
    }tr[500005];
    bool pd(int od,int wd,int x){
    	double y1=s[od].k*x+s[od].b;
    	double y2=s[wd].k*x+s[wd].b;
    	if(abs(y1-y2)<eps) return od>wd;
    	return y1<y2;
    }
    void insert(int &k,int l,int r,int x,int y,int c){
    	if(!k) k=++cnt;
    	if(l>=x&&r<=y){
    		if(pd(tr[k].c,c,l)&&pd(tr[k].c,c,r)){
    			tr[k].c=c;return;
    		}
    		if(pd(c,tr[k].c,l)&&pd(c,tr[k].c,r)) return;
    		int mid=l+r>>1;
    		if(pd(tr[k].c,c,mid)) swap(tr[k].c,c);
    		if(pd(tr[k].c,c,l)) insert(tr[k].lc,l,mid,x,y,c);
    		else insert(tr[k].rc,mid+1,r,x,y,c);
    		return;
    	}
    	if(l>y||r<x) return;
    	if(l==r) return;
    	int mid=l+r>>1;
    	if(x<=mid) insert(tr[k].lc,l,mid,x,y,c);
    	if(y>mid) insert(tr[k].rc,mid+1,r,x,y,c);
    }
    int find(int k,int l,int r,int x){
    	if(l==r&&l==x) return tr[k].c;
    	int mid=l+r>>1,re=0;
    	if(x<=mid) re=find(tr[k].lc,l,mid,x);
    	else re=find(tr[k].rc,mid+1,r,x);
    	if(pd(re,tr[k].c,x)) re=tr[k].c;
    	return re;
    }
    int main()
    {
    	scanf("%d",&n);
    	for(int i=1,op,v1,v2,w1,w2;i<=n;i++){
    		scanf("%d",&op);
    		if(op==1){
    			scanf("%d%d%d%d",&v1,&w1,&v2,&w2);
    			v1=(v1+ans-1)%mod1+1;
    			v2=(v2+ans-1)%mod1+1;
    			w1=(w1+ans-1)%mod2+1;
    			w2=(w2+ans-1)%mod2+1;
    			if(v1>v2){
    				swap(v1,v2);swap(w1,w2);
    			}
    			if(v1==v2){
    				s[++s1]=(node){0,max(w1,w2)};
    			}
    			else{
    				double k=(w2-w1)*1.0/(v2-v1);
    				s[++s1]=(node){k,w1-v1*k};
    			}
    			insert(rt,1,40000,v1,v2,s1);
    		}
    		else{
    			scanf("%d",&v1);
    			v1=(v1+ans-1)%mod1+1;
    			ans=find(rt,1,40000,v1);
    			printf("%d\n",ans);
    		}
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    维护直线

    与维护线段的方法类似,还要简单一些。不用考虑线段没有覆盖整个区间的情况,再按维护线段的方法实现即可。

    修改时直线只分配到 1 1 1个线段树上的区间(就是整个线段树维护的区间),这个区间需要 O ( log ⁡ n ) O(\log n) O(logn)的时间复杂度下传,所以单次修改的时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn)。同样地,查询也是 O ( log ⁡ n ) O(\log n) O(logn)

    例题

    P4254 [JSOI2008] Blue Mary 开公司

    也是一道模板题,在维护线段的代码的基础上删掉线段没有覆盖整个区间的情况即可。时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)

    code

    #include
    using namespace std;
    const double eps=1e-8;
    int n,s1,cnt=0,rt,ans;
    char ch[15];
    struct node{
    	double k,b;
    }s[100005];
    struct tree{
    	int lc,rc,c;
    }tr[500005];
    bool pd(int od,int wd,int x){
    	double w1=s[od].k+x*s[od].b;
    	double w2=s[wd].k+x*s[wd].b;
    	if(abs(w1-w2)<eps) return od>wd;
    	return w1<w2;
    }
    void insert(int &k,int l,int r,int c){
    	if(!k) k=++cnt;
    	if(pd(tr[k].c,c,l)&&pd(tr[k].c,c,r)){
    		swap(tr[k].c,c);return;
    	}
    	if(pd(c,tr[k].c,l)&&pd(c,tr[k].c,r)) return;
    	int mid=l+r>>1;
    	if(pd(tr[k].c,c,mid)) swap(tr[k].c,c);
    	if(pd(tr[k].c,c,l)) insert(tr[k].lc,l,mid,c);
    	else insert(tr[k].rc,mid+1,r,c);
    }
    int find(int k,int l,int r,int x){
    	if(l==r&&l==x) return tr[k].c;
    	int mid=l+r>>1,re=0;
    	if(x<=mid) re=find(tr[k].lc,l,mid,x);
    	else re=find(tr[k].rc,mid+1,r,x);
    	if(pd(re,tr[k].c,x)) re=tr[k].c;
    	return re;
    }
    int main()
    {
    	scanf("%d",&n);
    	s[0]=(node){0,-1e9};
    	for(int i=1,x;i<=n;i++){
    		scanf("%s",ch);
    		if(ch[0]=='P'){
    			double k,b;
    			scanf("%lf%lf",&k,&b);
    			s[++s1]=(node){k-b,b};
    			insert(rt,1,50000,s1);
    		}
    		else{
    			scanf("%d",&x);
    			ans=find(rt,1,50000,x);
    			ans=s[ans].k+x*s[ans].b;
    			if(ans<0) ans=0;
    			printf("%d\n",ans/100);
    		}
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
  • 相关阅读:
    2022年Java秋招面试必看的 | RabbitMQ 面试题
    java计算机毕业设计智能办公管理系统源程序+mysql+系统+lw文档+远程调试
    Java多态实现原理
    嵌入式Linux应用开发-驱动大全-第一章同步与互斥②
    (2)数据库mongodb 终端 和 vscode创建数据库 数据导入导出
    Java 设计模式之桥接模式
    【网络】网络编程——带你手搓简易TCP服务端(echo服务器)+客户端(四种版本)
    Python 集合
    ORB-SLAM2 ---- ExtractorNode::DivideNode函数
    嵌入式经典面试题
  • 原文地址:https://blog.csdn.net/tanjunming2020/article/details/133043817