手把手教你李超线段树

李超线段树

昨天模拟考了个cdq分治套李超线段树
我发现我连前置知识都不知道 我淦 于是乎辣鸡yxy来填坑了

定义

李超线段树是用来维护二维平面中 有若干条直线 询问直线 x = k x=k x=k与其他直线相交的点中 最大(最小)的 y y y 坐标

一般用来动态维护凸包(比啥平衡树 单调队列更香)

接下来默认维护最大值

每个区间维护的是一条直线

线段树上每个点对应一个区间 每个区间维护一条直线

我们询问直线 x = k x=k x=k 取得的最值是 答案就是在叶子节点 [ k , k ] [k,k] [k,k] 到根 [ 1 , n ] [1,n] [1,n] 的路径上的点中 维护的直线对应的 y y y 坐标最大的值

那么每个区间维护的是哪一条直线呢

就是维护这个区间内的所有直线中,从上往下能够看到的最长的那个线段对应的那条直线,也就是没有被其他直线覆盖长度最大的段。—yyb

比如这个平面中
在这里插入图片描述

区间 [ l , r ] [l,r] [l,r] 从上往下看 看到的最长的线段是加粗的红色线段

那么区间 [ l , r ] [l,r] [l,r] 维护的直线 便是那条红色直线

建树

建树的过程是一条一条直线插入进去的(这也是为什么可以动态维护凸包)

考虑怎么插入一条直线

假设我们要把直线插入区间 [ l , r ] [l,r] [l,r]

若区间本来没有存直线 那么存下这个直线 然后直接返回

若原本有直线如图

在这里插入图片描述

我们插入一条直线则有两种情况

在这里插入图片描述

• ① 新直线比原本直线高的部分>新直线比原本直线矮的部分

• 那么这个区间的直线更改为新直线

• 由于旧直线依旧可能产生贡献

• 所以我们把旧直线插入 [ l , r ] [l,r] [l,r] 的儿子区间(儿子区间要求包含旧直线高于新直线的区间

在这里插入图片描述

• ② 新直线比原本直线高的部分<=新直线比原本直线矮的部分

• 意味着这个区间不能更改为新直线

• 那么把新直线插入儿子区间(儿子区间要求包含新直线高于旧直线的区间

bool cmp(int i,int j,int kk,int bb,int x){ return (i*x+j)>=(kk*x+bb); }
void updata(int kk,int bb,int rt=1,int l=1,int r=nn){
	if(l==r){ if(cmp(kk,bb,k[rt],b[rt],l)) k[rt]=kk,b[rt]=bb; return; }
	if(cmp(kk,bb,k[rt],b[rt],l)){
		if(cmp(kk,bb,k[rt],b[rt],mid)){
			if(cmp(kk,bb,k[rt],b[rt],r)){ k[rt]=kk; b[rt]=bb; return; }
			else{ updata(k[rt],b[rt],rson); k[rt]=kk; b[rt]=bb; return; }
		}
		else{ updata(kk,bb,lson); return; }
	} 
	if(cmp(kk,bb,k[rt],b[rt],r)){
		if(cmp(kk,bb,k[rt],b[rt],mid+1)){ updata(k[rt],b[rt],lson); k[rt]=kk; b[rt]=bb; return; }
		else{ updata(kk,bb,rson); return; }
	}
}

查询

用对应叶子节点到根节点的路径上区间的直线更新答案

int query(int pos,int rt=1,int l=1,int r=nn){
	int res=k[rt]*pos+b[rt];
	if(l==r)return res;
	if(pos<=mid)res=max(res,query(pos,lson));
	else res=max(res,query(pos,rson));
	return res;
}

时间复杂度

这个其实挺显然的 插入是 O ( log ⁡ n ) O(\log n) O(logn) 的 查询也是是 O ( log ⁡ n ) O(\log n) O(logn)
整体复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

模板

struct LiChao_tree{
	int k[N<<2],b[N<<2];
	inline bool cmp(int i,int j,int kk,int bb,int x){ 
		return (i*x+j)>=(kk*x+bb);
	}
	inline void updata(int kk,int bb,int rt=1,int l=1,int r=nn){
		if(l==r){ if(cmp(kk,bb,k[rt],b[rt],l)) k[rt]=kk,b[rt]=bb; return; }
		if(cmp(kk,bb,k[rt],b[rt],l)){
			if(cmp(kk,bb,k[rt],b[rt],mid)){
				if(cmp(kk,bb,k[rt],b[rt],r)){ k[rt]=kk; b[rt]=bb; return; }
				else{ updata(k[rt],b[rt],rson); k[rt]=kk; b[rt]=bb; return; }
			}
			else{ updata(kk,bb,lson); return; }
		} 
		if(cmp(kk,bb,k[rt],b[rt],r)){
			if(cmp(kk,bb,k[rt],b[rt],mid+1)){ updata(k[rt],b[rt],lson); k[rt]=kk; b[rt]=bb; return; }
			else{ updata(kk,bb,rson); return; }
		}
	}
	inline int query(int pos,int rt=1,int l=1,int r=nn){
		int res=k[rt]*pos+b[rt];
		if(l==r)return res;
		if(pos<=mid)res=max(res,query(pos,lson));
		else res=max(res,query(pos,rson));
		return res;
	}
};

例题 PS:我全放的斜率优化

「BZOJ4518」 [Sdoi2016]征途

「ZJOI2007」仓库建设

「APIO2010」特别行动队

「JSOI2011」柠檬

「NOI2007」货币兑换

「NOI2019」回家路线

「NOI2016」国王饮水记

「NOI2014」购票

拓展

开头说的那个cdq套李超线段树 牛牛的RPG游戏

posted @ 2022-10-10 20:19  缙云山车神  阅读(49)  评论(0编辑  收藏  举报