李超线段树
单调栈/单调队列确实强势,但我不愿意动脑子。
原理
我们以模板题为例讲解李超线段树工作原理。
首先我们有一个基于值域的线段树。
称一个线段在区间 \([l,r]\) 中最优当且仅当该线段完全覆盖 \([l,r]\) 且在 \(mid\) 处的值为所有线段最大。
线段树上每个节点保存对应区间的最优线段。注意:区间的线段不能保证对于区间内所有点都取到最优。
考虑在一个区间插入一个线段 \(f\),若原区间有最优线段,设其为 \(f'\):
-
若区间内原本没有线段,则直接令 \(f\) 成为最优线段;
-
若 \(f\) 完全位于 \(f'\) 上方(即“\(f\) 严格优于 \(f'\)”),直接替换掉;
-
反之,\(f\) 不可能再成为最优线段,停止递归;
-
若 \(f\) 部分优于 \(f'\),则二者交点必然在区间内。按区间 \(mid\) 分开,则此时必然是:
-
一个子区间内存在“严格优于”的关系;
-
另一个子区间内是二者交点。递归更新。
-
对于第 3 种情况,不是区间最优线段的线段也有可能成为这个区间的答案。
单次查询操作时间复杂度 \(O(\log n)\),全局修改时间复杂度 \(O(\log n)\),区间修改时间复杂度 \(O(\log^2n)\)。李超线段树的常数较小。
code
template<typename T>
inline T Abs(const T &x){return x<0?-x:x;}
constexpr int N=1e5+10;
constexpr double eps=1e-9;
struct line{
double k,b;
int id;
inline double Y(int x){return x*k+b;}
};
struct segmenttree{
bool flag;
line L;
}tree[N<<2];
#define ls (u<<1)
#define rs (u<<1|1)
void modify(int u,int l,int r,int x,int y,line p){
if(x<=l&&y>=r){
double ly1=tree[u].L.Y(l),ry1=tree[u].L.Y(r);
double ly2=p.Y(l),ry2=p.Y(r);
if(!tree[u].flag) tree[u].L=p,tree[u].flag=1;
else if(ly2-ly1>eps&&ry2-ry1>eps) tree[u].L=p;
else if(ly2-ly1>eps||ry2-ry1>eps){
int mid=(l+r)>>1;
double midy1=tree[u].L.Y(mid);
double midy2=p.Y(mid);
if(midy2-midy1>eps) swap(tree[u].L,p);
if(p.Y(l)>tree[u].L.Y(l)) modify(ls,l,mid,x,y,p);
else modify(rs,mid+1,r,x,y,p);
}
return;
}
int mid=(l+r)>>1;
if(x<=mid) modify(ls,l,mid,x,y,p);
if(y>mid) modify(rs,mid+1,r,x,y,p);
}
struct Res{double y;int id;};
Res query(int u,int l,int r,int x){
Res res={tree[u].L.Y(x),tree[u].L.id};
if(l==r) return res;
int mid=(l+r)>>1;
if(x<=mid){
Res resl=query(ls,l,mid,x);
if(resl.y>res.y||(Abs(resl.y-res.y)<eps&&resl.id<res.id))
res=resl;
}
else{
Res resr=query(rs,mid+1,r,x);
if(resr.y>res.y||(Abs(resr.y-res.y)<eps&&resr.id<res.id))
res=resr;
}
return res;
}

浙公网安备 33010602011771号