Loading

【学习笔记】李超线段树

Page Views Count

维护一次函数

模板题 为例。

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

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

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

具体流程如下:

先比较 \(mid\) 处点值,钦定 \(f\)\(mid\) 处点值不劣的线段。

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

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

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

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

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

点击查看代码
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\) 不具有单调性时,就需要用到李超线段树。

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

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

例题

Luogu-P4254 JSOI 2008 Blue Mary 开公司

维护一次函数模板。

Luogu-P2497 SDOI 2012 基站建设

运用几何知识可以得到:\(\sqrt{r_i'}=\dfrac{x_i-x_j}{2\sqrt{r_j}}\)

转移方程:

\[f_i=v_i+\min_{j=1}^{i-1} f_j+\dfrac{x_i-x_j}{2\sqrt{r_j}} \]

移项后意识到 \(X_j=-\dfrac{1}{2\sqrt{r_j}},Y_j=f_j-\dfrac{X_j}{2\sqrt{r_j}}\),李超线段树维护。(离散化或动态开点均可)

参考资料

posted @ 2023-07-09 19:04  SoyTony  阅读(88)  评论(1编辑  收藏  举报