李超树学习笔记
按理说很久之前就该写了,只不过一直鸽着,突然考了一次发现自己不会了就再看看,不过这个东西也是着实很妙哉的
李超树支持两个操作,一个是插入一条 \(y=kx+b\) 的直线 \(or\) 线段 \((l<=x<=r)\)
另一个是查询\(x\)在所有插入直线里的最优值
不过这里的\(x\)一定要是离散化后有个数限制的,因为我们要以\(x\)为下标开一棵线段树,否则我也不知道怎么做了
或许就有更巧妙的办法了
首先我们对于线段树上的一个区间维护这个区间mid的最优线段
现在我们插入一条线段 \(or\) 直线
对于这条直线,如果以\(l\)和以\(r\)为\(x\)都比当前的这条直线更优,直接替换这条直线然后\(return\)即可
那么如果都劣的话这条直线就是个垃圾,也直接\(return\)就好
考虑这条直线与该区间的直线有交点的情况
先判断\(mid\)的取值情况
如果当前直线的值在 \(mid\) 的情况比原来更优,那么显然在其中一侧,当前直线比原来直线肯定全部更优,另一侧可能有交替
那么让在 \(mid\) 更劣的那条直线去更新更优的区间,只会在一侧递归下去
复杂度是\(O(log(n))\)的,如果是线段的话复杂度就是\(O(log(n)^2)\)的
查询的话就是经典的标记永久化的查询
粘个大板
struct node{int k,b;};
const int maxn=1e5+5,inf=-1e9;
struct seg{
#define lid (id<<1)
#define rid (id<<1|1)
node tr[maxn<<2];
inline int calc(node a,int x){return a.k*x+a.b;}
inline bool cover(node a,node b,int x){return calc(a,x)<=calc(b,x);}//这里看维护最小值还是最大值了,这里维护最大值
inline void insert(int id,int l,int r,int ll,int rr,node New)
{
if(l>=ll&&r<=rr)
{
if(cover(tr[id],New,l)&&cover(tr[id],New,r)) return tr[id]=New,void();
int mid=(l+r)>>1;
if(cover(tr[id],New,mid)) swap(tr[id],New);//比一下mid的,让New为要更新的那条线段
if(cover(tr[id],New,l)) insert(lid,l,mid,ll,rr,New);//找到更优的那部分进行更新
if(cover(tr[id],New,r)) insert(rid,mid+1,r,ll,rr,New);
return ;
}
int mid=(l+r)>>1;
if(ll<=mid) insert(lid,l,mid,ll,rr,New);
if(rr>mid) insert(rid,mid+1,r,ll,rr,New);
}
inline int query(int id,int l,int r,int pos)
{
int mid=(l+r)>>1; int ans=-inf;
if(pos<mid) ans=max(ans,query(lid,l,mid,pos));//如果这里是插入的是直线的话是不需要取等于号的,但插入的如果是线段的话记得取等
if(pos>mid) ans=max(ans,query(rid,mid+1,r,pos));
return ans=max(ans,calc(tr[id],pos));
}
}T;
例题:
[HEOI2013]Segment
比较裸的李超树题目,求出那条线段的一次项系数和常数项插入就行了,注意下 \(x_{0}=s_{1}\) 的情况
[JSOI2008]Blue Mary开公司
同理,只不过读入的方式和上个不一样,并且从第二天才开始有斜率
洛谷P4655[CEOI2017]Building Bridges
有一个非常明显的dp转移
\(dp_{i}=min(dp_{j}+(h_{i}-h_{j})^2+s_{i-1}-s_{j})\)
那么\(dp_{j}+s_{j}\)显然是一个常数项,然后我们考虑将这个 \((h_{i}-h_{j})^2\) 拆开
就是\(h_{i}^2-2h_{i}h_{j}+h_{j}^2\)那么这个转移显然就变成了\(k=-2h_{j}\)的斜率柿子
可以斜率优化,也可以李超树维护