【学习笔记】李超线段树

Page Views Count

维护一次函数#

模板题 为例。

使用线段树维护线段,每个节点维护的都是完全覆盖这个区间的线段。

考虑当前节点已经有线段 f,现在加入线段 g

暴力想法是暴力递归每个子区间,把更优的保留,注意到 f,g 最多一个交点,因此也最多一侧的子区间需要暴力递归。

具体流程如下:

先比较 mid 处点值,钦定 fmid 处点值不劣的线段。

  • l 处点值 g 更优,说明交点在左区间,那么右区间更优的仍为 f,左区间递归处理。

  • r 处点值 g 更优,说明交点在右区间,那么左区间更优的仍未 g,右区间递归处理。

  • 特别地,若 lr 处点值相等,且新增加的线段在 mid 处点值更优,那么就递归处理对应的区间。

  • 处理之后保留在当前节点的线段应当为 f,即 mid 处点值不劣的线段。

这样类似于标记永久化,查询时递归每个区间,总复杂度 O(nlog2n)。(如果添加直线就是 O(nlogn)。)

点击查看代码
int n,X=39989,Y=1000000000;

struct Line{
    db k,b;
    Line()=default;
    Line(db k_,db b_):k(k_),b(b_){}
}L[maxn];
int tot;

inline db get_y(int id,int x){
    if(!id) return 0;
    return L[id].k*x+L[id].b;
}
inline int check(int id1,int id2,int x){
    db y1=get_y(id1,x),y2=get_y(id2,x);
    if(y1-y2>eps) return 1;
    else if(y2-y1>eps) return -1;
    else return 0;
}
inline pdi max(pdi A,pdi B){
    if(A.fir-B.fir>eps) return A;
    else if(B.fir-A.fir>eps) return B;
    else{
        if(A.sec<B.sec) return A;
        else return B;
    }
}
struct SegmentTree{
#define mid ((l+r)>>1)
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
    int tag[maxn<<2];
    void update(int rt,int l,int r,int id){
        if(!tag[rt]) return tag[rt]=id,void();
        if(check(id,tag[rt],mid)==1) swap(tag[rt],id);
        int chkl=check(id,tag[rt],l),chkr=check(id,tag[rt],r);
        if(chkl==1||(!chkl&&id<tag[rt])) update(lson,id);
        if(chkr==1||(!chkr&&id<tag[rt])) update(rson,id);
    }
    void insert(int rt,int l,int r,int pl,int pr,int id){
        if(pl<=l&&r<=pr){
            update(rt,l,r,id);
            return;
        }
        if(pl<=mid) insert(lson,pl,pr,id);
        if(pr>mid) insert(rson,pl,pr,id);
    }
    pdi query(int rt,int l,int r,int x){
        db y=get_y(tag[rt],x);
        pdi res=make_pair(y,tag[rt]);
        if(l==r) return res;
        if(x<=mid) res=max(res,query(lson,x));
        else res=max(res,query(rson,x));
        return res;
    }
#undef mid
#undef lson
#undef rson
}S;

int lastans;

int main(){
    n=read();
    while(n--){
        int opt=read();
        if(opt==1){
            int x0=read(),y0=read(),x1=read(),y1=read();
            x0=(x0+lastans-1)%X+1,y0=(y0+lastans-1)%Y+1,x1=(x1+lastans-1)%X+1,y1=(y1+lastans-1)%Y+1;
            if(x0>x1) swap(x0,x1),swap(y0,y1);
            if(x0==x1) L[++tot]=Line(0,max(y0,y1));
            else{
                db k=1.0*(y1-y0)/(x1-x0),b=y0-k*x0;
                L[++tot]=Line(k,b);
            }
            S.insert(1,1,X,x0,x1,tot);
        }
        else{
            int x=read();
            x=(x+lastans-1)%X+1;
            lastans=S.query(1,1,X,x).sec;
            printf("%d\n",lastans);
        }
    }
    return 0;
}

斜率优化 DP#

斜率优化 DP 中,通常写成 y=kx+b 的形式,其中 y,x 均与决策点 j 有关,通常是使用数据结构维护凸包。

x 具有单调性的前提下,k 具有单调性的情况,可以单调队列维护;反之可以单调栈维护并二分。

x 不具有单调性时,就需要用到李超线段树。

直接抛弃前面对凸包的依赖,我们所要找的实际上过 (xj,yj) 的且斜率为 k 直线中,截距满足最值要求的一个。直接移项成 b=xj×x+yj,这样就是把 x=k 代入求最值了,可以李超线段树维护。

因此李超线段树可以解决没有任何单调性的斜率优化。

例题#

Luogu-P4254 JSOI 2008 Blue Mary 开公司#

维护一次函数模板。

Luogu-P2497 SDOI 2012 基站建设#

运用几何知识可以得到:ri=xixj2rj

转移方程:

fi=vi+minj=1i1fj+xixj2rj

移项后意识到 Xj=12rj,Yj=fjXj2rj,李超线段树维护。(离散化或动态开点均可)

参考资料#

作者:SoyTony

出处:https://www.cnblogs.com/SoyTony/p/Learning_Notes_about_Li-Chao_Segment_Tree.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   SoyTony  阅读(107)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示