李超线段树学习笔记

本文介绍了李超线段树这一高级数据结构,主要用于二维平面直角坐标系中动态插入线段并查询与给定直线相交的y值最大/最小值。文章详细讲解了李超线段树的主要思想、实现方法,并通过三道典型例题展示了其实战应用,包括JSOI2008的Blue Mary开公司、HEOI2013的Segment和SDOI2016的游戏问题。
摘要由CSDN通过智能技术生成

李超线段树

介绍

  李超线段树作为一种高级数据结构,其最经典的应用就是维护二维平面直角坐标系中,支持动态插入线段,(不支持删除线段),询问已插入直线中与直线 x = x 0 x=x_0 x=x0 相交 y y y 值最大/最小值。其中插入直线复杂度 O ( n l o g n ) O(nlogn) O(nlogn),在指定区间插入线段复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

主要思想

  对于上述问题,暴力做法显然就是 O ( n ) O(n) O(n) 的遍历所有已插入线段,比较交点的 y y y 值。这个做法复杂度的瓶颈在于我们的“最优解“集合过大,实际上有些线段根本不可能成为最优解,那么李超线段树正是运用这种缩小最优解集合的方法降低复杂度。

  首先,李超线段树中每个区间只记录在当前区间可能成为最优解的线段,即该线段在当前区间的某个取值上有最优解,我们称这样的线段为优势线段。如何维护区间内的优势线段就成了李超数的核心问题,我们将使用线段树的框架并且利用标记永久化这一方法,接着来具体分析这一问题:

我们以求最大值为例进行分析:

1.当区间内没有任何线段时:我们直接将线段放入当前区间即可

2.当区间内存在旧线段,且新线段的斜率大于旧线段的斜率时,设当前区间中点为 m i d mid mid

  • 如果新线段在中点的值大于旧线段,那么新线段在 [ m i d + 1 , R ] [mid+1,R] [mid+1,R] 上整体取值都比旧线段更优,我们直接用新线段替换旧线段,并且此时旧线段仍可能在 [ L , m i d ] [L,mid] [L,mid] 区间有优势,我们把旧线段下放至 [ L , m i d ] [L,mid] [L,mid] 区间即可
  • 如果新线段在中点的值小于旧线段,那么旧线段在 [ L , m i d ] [L,mid] [L,mid] 上整体取值一定都比旧线段更优, 而在 [ m i d + 1 , R ] [mid+1,R] [mid+1,R] 区间新线段仍可能存在优势,所以旧线段仍然是 [ L , R ] [L,R] [L,R] 的优势线段,所以我们保留旧线段,下放线段至 [ m i d + 1 , R ] [mid+1,R] [mid+1,R]

3.当区间内存在旧线段,且新线段的斜率小于旧线段的斜率时同上

4.当区间内存在旧线段,且新线段的斜率相同时,显然我们只需要保留截距更大的线段即可

5.上述过程已经可以很好的维护优势线段,但是在此之前我们可以先判断两条线段是否存在严格优势关系,如果存在我们可以直接排除另一条线段,这样我们在情况 2,3 时就可以保证下放的线段不是毫无用处的,相当于做一个小剪枝

可以发现上述过程中我们维护的所谓优势线段,并不是严格的,复杂度因此有了保障。但是我们在查询的时候显然不能只递归到一个叶子节点,我们必须在向下递归的同时计算经过的线段的最值,即利用标记永久化来得到最优解。

实现

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int maxn=1e6+10;
const int maxm=2e5+10;
const int p=998244353;

int k[maxm],b[maxm];
int tag0[maxn<<2],tag1[maxn<<2];
int n,m,tot=0;

inline ll cal(int i,int x){
   
    return 1ll*k[i]*x+b[i];
}

void modify_min(int l,int r,int x,int i){
   
    if(!tag0[x]){
   tag0[x]=i;return;}
    if(l==r){
   
        if(cal(tag0[x],l)>cal(i,l)) tag0[x]=i;
        return;
    }
    int mid=l+r>>1;
    ll y1=cal(tag0[x],mid),y2=cal(i,mid);
    if(k[tag0[x]]>k[i]){
   
        if(y1<=y2) modify_min(mid+1,r,x<<1|1,i);
        else modify_min(l,mid,x<<1,tag0[x]),tag0[x]=i;
    }
    else if(k[tag0[x]]<k[i]){
   
        if(y1<=y2) modify_min(l,mid,x<<1,i); 
        else modify_min(mid+1,r,x<<1|1,tag0[x]),tag0[x]=i;
    }
    else if(b[tag0[x]]>b[i]) tag0[x]=i;
}

void modify_max(int l,int r,int x,int i){
   
    if(!tag1[x]){
   tag1[x]=i;return;}
    if(l==r){
   
        if(cal(tag1[x],l)<cal(i,l)) tag1[x]=i;
        return;
    }
    int mid=l+r>>1;
    ll y1=cal(tag1[x],mid),y2=cal(i,mid);
    if(k[tag1[x]]>k[i]){
   
        if(y1<=y2) modify_max(mid+1,r,x<<1|1,tag1[x]),tag1[x]=i;
        else modify_max(l,mid,x<<1,i);
    }
    else if(k[tag1[x]]<k[i]){
   
        if(y1<=y2) modify_max(l,mid,x<<1,tag1[x]),tag1[x]=i;
        else modify_max(mid+1,r,x<<1|1,i);
    }
    else if(b[tag1[x]]<b[i]) tag1[x]=i;
}

ll query_min(int l,int r,int x,int pos){
   
    if(tag0[x]==0) return 1e18;
    if(l==r) return cal(tag0[x],pos);
    int mid=l+r>>1;
    ll ans=cal(tag0[x],pos);
    if(pos<=mid) return min(ans,query_min(l,mid,x<<1,pos));
    else return min(ans,query_min(mid+1,r,x<<1|1,pos));
}

ll query_max(int l,int r,int x,int pos){
   
    if(tag1[x]==0) return -1e18;
    if(l==r) return cal(tag1[x],pos);
    int mid=
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值