[DS 小计] 李超线段树
前言
大家好啊,今天讲的是 LCT (李超 Tree)(bushi
错了错了。这两不是一个东西。
概念
李超树能干什么。
- 插入一条直线/线段
- 单点查询当前点的峰值 (最大最小均可)
你会说:
OI 中有什么是和斜率相关的吗?
有,那就是斜率优化。
关于斜率优化可以看这个。
你会说:
你说的对,静态的 dp,CDQ 写的好常数小 直接薄砂李超树。
所以李超树的优点就出来了:
- 好写好想,凸包啥的什么都不管。
- 是动态的
若是动态的,谁也不想写平衡树把……而且难维护。
所以,这就是 LCT!(李超树)(bushi
可是它的英文名就是 Li Chao Tree 啊。
前置芝士
- 初中的数学
- 动态开点线段树(因为有时候值域会飞天的大 有可能到 这个时候会不会平衡树维护凸包更好?)
模板:This (Upda:关于动态开点的数组大小问题,如果是单点修改的话请开 ,区间的话建议能开多大开多开。如果实在卡, 理论上是 ,常数是 。不过这个有点玄学。 - 标记永久化
单点查询的优势就是这个。所以看到单点查询可以思考一下这个。
模板:This
算法思路
我们考虑标记永久化。
每次下标,只到完全覆盖节点即可。
那么,线段树每个节点存的是什么呢?
是一条可能被用来更新最优值的线段。
我们分类讨论。
- 情况 :没有交点
假设黑色线段是我们的旧标记,蓝色线段是我们的新标记。
这个时候黑色线段什么用都没用,直接覆盖标记即可。
同理,如果蓝色是先前的,黑色是新来的,那么新线段没用,直接丢弃即可。
- 情况 :有交点
我们比较一下中点处。
蓝色是新来的,如果在中点处比当前线段优,我们认为这条线段是当前这个节点最优的。
是这样的话,我们把当前线段作为标记,标记线段下传。
实际上,覆盖后,更新的线段只有红色一部分。
所以直接覆盖右区间,分类讨论即可。
其它情况同理推一推不难。
代码很简单。
点击查看代码
#include<bits/stdc++.h>
#define N 100005
#define ll long long
#define ld double
using namespace std;
const ld eps=1e-9;
const int mod =39989;
const int mod2=1e9;
struct Tnode{
ld k,b;int id;
};
inline ld K(int x1,int y1,int x2,int y2){return (y1-y2)*1.0/(x1-x2);}
inline ld getv(Tnode a,int x){return a.k*x+a.b;}
inline bool cmp(Tnode a,Tnode b,int x)
{
ld v1=getv(a,x),v2=getv(b,x);
if(fabs(v1-v2)<eps) return a.id<b.id;
if(v1-v2>eps) return 1;
else return 0;
}
struct segtree{
Tnode tr[mod*4];
void updata(int l,int r,int x,Tnode v)
{
if(!tr[x].id||cmp(v,tr[x],l)&cmp(v,tr[x],r))
{
tr[x]=v;
return;
}
if(l==r) return;
int mid=(l+r)/2;
if(cmp(v,tr[x],mid)) swap(v,tr[x]);
if(cmp(v,tr[x],l)) updata(l,mid,x*2,v);
if(cmp(v,tr[x],r)) updata(mid+1,r,x*2+1,v);
}
void modify(int l,int r,int L,int R,int x,Tnode v)
{
if(l>R||r<L) return ;
if(l>=L&&r<=R)
{
updata(l,r,x,v);
return;
}
int mid=(l+r)/2;
modify(l,mid,L,R,x*2,v);
modify(mid+1,r,L,R,x*2+1,v);
}
Tnode query(int l,int r,int p,int x)
{
if(l==r) return tr[x];
int mid=(l+r)/2;
Tnode v;
if(p<=mid) v=query(l,mid,p,x*2);
else v=query(mid+1,r,p,x*2+1);
return cmp(v,tr[x],p)?v:tr[x];
}
void ins(int x1,int y1,int x2,int y2,int id)
{
Tnode t;
if(x1==x2) t.k=0,t.b=max(y1,y2);
else t.k=K(x1,y1,x2,y2),t.b=y1-t.k*x1;
t.id=id;
if(x1>x2) swap(x1,x2);
modify(1,mod,x1,x2,1,t);
}
}tr;
int n,m,last;
int main()
{
scanf("%d",&m);
while(m--)
{
int opr,x1,x2,y1,y2,v;
scanf("%d",&opr);
if(opr)
scanf("%d%d%d%d",&x1,&y1,&x2,&y2),
x1=(x1+last-1)%mod+1,
x2=(x2+last-1)%mod+1,
y1=(y1+last-1)%mod2+1,
y2=(y2+last-1)%mod2+1,
tr.ins(x1,y1,x2,y2,++n);
else
scanf("%d",&v),
v=(v+last-1)%mod+1,
printf("%d\n",last=tr.query(1,mod,v,1).id);
}
return 0;
}
我们来分析时间复杂度。
明显,每个区间会拆分成 个区间,每次会下传至多 层。总的时间复杂度下传是 的,常数有个 ,但这么多浮点数的计算也是大常数.....
查询是明显 的
如果是插入直线,那么是优秀复杂度
这就是看情况使用。
实际上,李超写无递增的斜率优化又无脑又好写。
所以,面对无递增斜率优化,李超线段树是很好的选择。 很好用。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)