[DS 小计] 李超线段树

前言

大家好啊,今天讲的是 LCT (李超 Tree)(bushi
错了错了。这两不是一个东西。

概念

李超树能干什么。

  • 插入一条直线/线段
  • 单点查询当前点的峰值 (最大最小均可)

你会说:

OI 中有什么是和斜率相关的吗?

有,那就是斜率优化。
关于斜率优化可以看这个

你会说:

你说的对,静态的 dp,CDQ 写的好常数小 nlogn 直接薄砂李超树。

所以李超树的优点就出来了:

  • 好写好想,凸包啥的什么都不管。
  • 动态的
    若是动态的,谁也不想写平衡树把……而且难维护。

所以,这就是 LCT!(李超树)(bushi
可是它的英文名就是 Li Chao Tree 啊。

前置芝士

  • 初中的数学
  • 动态开点线段树(因为有时候值域会飞天的大 有可能到 260 这个时候会不会平衡树维护凸包更好?)
    模板:This (Upda:关于动态开点的数组大小问题,如果是单点修改的话请开 qlogw,区间的话建议能开多大开多开。如果实在卡, 理论上是 qlogw,常数是 2。不过这个有点玄学。
  • 标记永久化
    单点查询的优势就是这个。所以看到单点查询可以思考一下这个。
    模板:This

算法思路

我们考虑标记永久化。
每次下标,只到完全覆盖节点即可。
那么,线段树每个节点存的是什么呢?
是一条可能被用来更新最优值的线段。
我们分类讨论。

  • 情况 1 :没有交点

image
假设黑色线段是我们的旧标记,蓝色线段是我们的新标记。
这个时候黑色线段什么用都没用,直接覆盖标记即可。
同理,如果蓝色是先前的,黑色是新来的,那么新线段没用,直接丢弃即可。

  • 情况 2 :有交点

我们比较一下中点处。
image
蓝色是新来的,如果在中点处比当前线段优,我们认为这条线段是当前这个节点最优的。
是这样的话,我们把当前线段作为标记,标记线段下传。
实际上,覆盖后,更新的线段只有红色一部分。
所以直接覆盖右区间,分类讨论即可。
其它情况同理推一推不难。

代码很简单。

点击查看代码
#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;
}

我们来分析时间复杂度。
明显,每个区间会拆分成 logw 个区间,每次会下传至多 logw 层。总的时间复杂度下传是 O(log2w) 的,常数有个 0.5,但这么多浮点数的计算也是大常数.....

查询是明显 O(logw)

如果是插入直线,那么是优秀复杂度 O(mlogw)

这就是看情况使用。

实际上,李超写无递增的斜率优化又无脑又好写。
所以,面对无递增斜率优化,李超线段树是很好的选择。 O(nlogw) 很好用。

posted @   g1ove  阅读(18)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
点击右上角即可分享
微信分享提示