手把手教你李超线段树
李超线段树
昨天模拟考了个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游戏