浅谈李超线段树
huangzirui · · 个人记录
引入:
有时候我们被给定一些直线,需要求
在某些斜率优化的题目中,这个问题经常出现。
一般的斜率优化题都满足一些单调性质,可以支持
但是在失去了某些单调性后,就需要用数据结构维护。
例:
给定一些柱子的高度
显然可以列出一个状态转移方程,设
其中
那么可以转化成一个类似斜率优化的式子:
可以把每个转移点抽象为一条斜率为
这个问题是李超线段树的经典问题,要解决它,我们先要了解李超线段树。
李超线段树
简单来说,李超线段树和线段树类似,都是每个节点维护
查询某个点
这里以 P4097 为例说明。
代码实现如下:
#define l(x) tree[x].l
#define ls(x) (tree[x].son[0])
#define rs(x) (tree[x].son[1])
#define ll long long
#define Mid ((L+R)>>1)
const double inf=1e18;
struct line{
double k,b;int id;
double calc(int x){return k*x+b;}
};
struct segment_tree{
int son[2];
line l;
}tree[maxn*8];
代码实现了直线和节点,其中
void insert(int &root,int L,int R,int l,int r,line g){
if(!root)root=++cnt,tree[root]={0,0,0};
if(L==l && R==r){
if(l(root).calc(Mid)<g.calc(Mid))swap(l(root),g);
if(L!=R){
if(l(root).k<g.k)insert(rs(root),Mid+1,R,Mid+1,r,g);
else if(l(root).k>g.k)insert(ls(root),L,Mid,l,Mid,g);
}return;
}
if(r<=Mid)insert(ls(root),L,Mid,l,r,g);
else if(l>Mid)insert(rs(root),Mid+1,R,l,r,g);
else insert(ls(root),L,Mid,l,Mid,g),insert(rs(root),Mid+1,R,Mid+1,r,g);
}
代码实现了插入一条线段,这个操作的复杂度是
首先,将一条线段分割为
然后判断是否可以取代原最优线段,如果是就把原最优线段下传,否则下传当前的线段,下传时需要分类讨论。
具体来说,如果当前的线段比最优线段差,假设当前线段是
(使用了 OI wiki 的图片)
如果当前线段斜率大于最优线段,那么只可能在右侧区间更优,直接下传。
同理,下传至左侧即可。
因此每条线段最多下传
注意:插入直线的复杂度为
inline line MAX(line a,line b,int x){
return a.calc(x)>b.calc(x) || (fabs(a.calc(x)-b.calc(x))<1e-9 && a.id<b.id)?a:b;
}
line find(int root,int L,int R,int x){
if(!root)return {0,0,0};
if(L==R)return l(root);
if(x<=Mid)return MAX(find(ls(root),L,Mid,x),l(root),x);
return MAX(find(rs(root),Mid+1,R,x),l(root),x);
}
代码实现了寻找一条
以上是李超线段树的基本操作。接下来讲一下例题:
例题
[HEOI2013]Segment
模板题,需要注意斜率不存在的情况。
[CEOI2017]Building Bridges
引入题。
这道题提示我们,在解决一些动态规划问题时,可以利用李超线段树维护转移过程。
一些其他的解决方法有 cdq 分治等,但是个人认为李超树更简单一些(
[SDOI2016]游戏
“总是有出题人喜欢把序列上用线段树解决的题目出到树上,让选手强行写个树链剖分或树分治或某种动态树数据结构。 这种行为已经很无趣了。 所以我们想让大家知道,不光可以放在静态树上,动态仙人掌也是可以的。”
期待有出题人把李超线段树放到动态仙人掌上(