资讯详情

李超线段树学习笔记

文章目录

  • 李超线段树
      • 介绍
      • 主要思想
      • 实现
      • 大量例题
        • P4254 - [JSOI2008]Blue Mary开公司
        • P4097 - [HEOI2013]Segment
        • P4069 - [SDOI2016]游戏

李超线段树

介绍

??作为一种先进的数据结构,李超线段树最经典的应用是维护二维平面直角坐标系,支持动态插入线段(不支持删除线段),询问已插入直线和直线 x = x 0 x=x_0 x=x0 相交 y y y 最大值/最小值。插入直线的复杂性 O ( n l o g n ) O(nlogn) O(nlogn),插入指定区间的线段的复杂性 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)。

主要思想

??对于上述问题,暴力显然是 O ( n ) O(n) O(n) 所有已插入线段并比较交点的遍历 y y y 值。这种做法复杂性的瓶颈在于我们的最佳解决方案太大,事实上,有些线段根本不能成为最佳解决方案,所以李超线段树使用这种方法来减少最佳解决方案的复杂性。

??首先,李超线段树的每个区间只记录,即该线段在当前区间的个取值上有最优解,我们称这样的线段为优势线段。如何维护区间内的优势线段就成了李超数的核心问题,我们将使用线段树的框架并且利用标记永久化这一方法,接着来具体分析这一问题:

1.当区间内没有任何线段时:我们直接将线段放入当前区间即可

2.当区间内存在旧线段,且新线段的斜率旧线段的斜率时,设当前区间中点为 m i d mid mid:

  • 如果新线段在中点的值旧线段,那么新线段在 [ m i d + 1 , R ] [mid+1,R] [mid+1,R] 上整体取值都比旧线段更优,我们直接用新线段替换旧线段,并且此时旧线段仍可能在 [ L , m i d ] [L,mid] [L,mid] 区间有优势,我们把旧线段下放至 [ L , m i d ] [L,mid] [L,mid] 区间即可
  • 如果新线段在中点的值旧线段,那么旧线段在 [ L , m i d ] [L,mid] [L,mid] 上整体取值一定都比旧线段更优, 而在 [ m i d + 1 , R ] [mid+1,R] [mid+1,R] 区间新线段仍可能存在优势,所以旧线段仍然是 [ L , R ] [L,R] [L,R] 的优势线段,所以我们保留旧线段,下放线段至 [ m i d + 1 , R ] [mid+1,R] [mid+1,R]

3.当区间内存在旧线段,且新线段的斜率旧线段的斜率时同上

4.当区间内存在旧线段,且新线段的斜率时,显然我们只需要保留截距更大的线段即可

5.上述过程已经可以很好的维护优势线段,但是在此之前我们可以先判断两条线段是否存在严格优势关系,如果存在我们可以直接排除另一条线段,这样我们在情况 2,3 时就可以保证下放的线段不是毫无用处的,相当于做一个小剪枝

可以发现上述过程中我们维护的所谓优势线段,并不是的,复杂度因此有了保障。但是我们在查询的时候显然不能只递归到一个叶子节点,我们必须在向下递归的同时计算经过的线段的最值,即利用标记永久化来得到最优解。

实现

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int maxn=1e6+10;
const int maxm=2e5+10;
const int p=998244353;

int k[maxm],b[maxm];
int tag0[maxn<<2],tag1[maxn<<2];
int n,m,tot=0;

inline ll cal(int i,int x){ 
        
    return 1ll*k[i]*x+b[i];
}

void modify_min(int l,int r,int x,int i){ 
        
    if(!tag0[x]){ 
        tag0[x]=i;return;}
    if(l==r){ 
        
        if(cal(tag0[x],l)>cal(i,l)) tag0[x]=i;
        return;
    }
    int mid=l+r>>1;
    ll y1=cal(tag0[x],mid),y2=cal(i,mid);
    if(k[tag0[x]]>k[i]){ 
        
        if(y1<=y2) modify_min(mid+1,r,x<<1|1,i);
        else modify_min(l,mid,x<<1,tag0[x]),tag0[x]=i;
    }
    else if(k[tag0[x]]<k[i]){ 
        
        if(y1<=y2) modify_min(l,mid,x<<1,i); 
        else modify_min(mid+1,r,x<<1|1,tag0[x]),tag0[x]=i;
    }
    else if(b[tag0[x]]>b[i]) tag0[x]=i;
}

void modify_max(int l,int r,int x,int i){ 
        
    if(!tag1[x]){ 
        tag1[x]=i;return;}
    if(l==r){ 
        
        if(cal(tag1[x],l)<cal(i,l)) tag1[x]=i;
        return;
    }
    int mid=l+r>>1;
    ll y1=cal(tag1[x],mid),y2=cal(i,mid);
    if(k[tag1[x]]>k[i]){ 
        
        if(y1<=y2) modify_max(mid+1,r,x<<1|1,tag1[x]),tag1[x]=i;
        else modify_max(l,mid,x<<1,i);
    }
    else if(k[tag1[x]]<k[i]){ 
        
        if(y1<=y2) modify_max(l,mid,x<<1,tag1[x]),tag1[x]=i;
        else modify_max(mid+1,r,x<<1|1,i);
    }
    else if(b[tag1[x]]<b[i]) tag1[x]=i;
}

ll query_min(int l,int r,int x,int pos){ 
        
    if(tag0[x]==0) return 1e18;
    if(l==r) return cal(tag0[x],pos);
    int mid=l+r>>1;
    ll ans=cal(tag0[x],pos);
    if(pos<=mid) return min(ans,query_min(l,mid,x<<1,pos));
    else return min(ans,query_min(mid+1,r,x<<1|1,pos));
}

ll query_max(int l,int r,int x,int pos){ 
        
    if(tag1[x]==0) return -1e18;
    if(l==r) return cal(tag1[x],pos);
    int mid=l+r>>1;
    ll ans=cal(tag1[x],pos);
    if(pos<=mid) return max(ans,query_max(l,mid,x<<1,pos));
    else return max(ans,query_max(mid+1,r,x<<1|1,pos));
}


int main(){ 
        
    scanf("%d %d",&n,&m);
    int opt;
    while(m--){ 
        
        scanf("%d",&opt);
        if(opt==0){ 
        
            ++tot;
            scanf("%d %d",&k[tot],&b[tot]);
            modify_min(1,n,1,tot);
            modify_max(1,n,1,tot);
        }
        else{ 
        
            int x;
            scanf("%d",&x);
            printf("%lld %lld\n",query_max(1,n,1,x),query_min(1,n,1,x));
        }
    }
    return 0;
}

上述代码同时用多个函数实现了最大最小值,其实只用一个求最大值的模板代替,当我们求求最小值的时候就把直线 y = k x + b y=kx+b y=kx+b 关于 x x x 轴对称的直线 y = − k x − b y=-kx-b y=−kx−b 插入, 我们对所有直线关于 x x x 轴的对称的直线的最大交点,就是求所有直线的最小交点,并且最后查询的时候返回一个值的相反数即可。

大量例题

两道板子题

P4254 - [JSOI2008]Blue Mary开公司

现在有 n n n 次询问,要求支持如下两种操作:

  • 1. 1. 1. 插入一个形如 y = k x + b y=kx+b y=kx+b 的一次函数
  • 2. 2. 2. 查询已插入直线中与直线 x = T x=T x=T 交点的最大 y y y 值

李超树模板,注意题目给出的截距要大 1 1 1,调整下即可。

code:

const int maxn=1e5+10;
const int n=50000;

int q,tot=0;
char ch[10];
int tag[maxn<<2];
double k[maxn],b[maxn];

inline double cal(int i,int x){ 
        
    return k[i]*(x-1)+b[i];
}

void modify(int l,int r,int x,int idx){ 
        
    if(l==r){ 
        
        if(cal(tag[x],l)<cal(idx,l)) tag[x]=idx;
        return;
    }
    if(!tag[x]){ 
        tag[x]=idx;return;}
    int mid=l+r>>1;
    double y1=cal(tag[x],mid),y2=cal(idx,mid);
    if(k[tag[x]]<k[idx]){ 
        
        if(y1<=y2) modify(l,mid,x<<1,tag[x]),tag[x]=idx;
        else modify(mid+1,r,x<<1|1,idx);
    }
    else if(k[tag[x]]>k[idx]){ 
        
        if(y1<=y2) modify(mid+1,r,x<<1|1,tag[x]),tag[x]=idx;
        else modify(l,mid,x<<1,idx);
    }
    else if(b[tag[x]]<b[idx]) tag[x]=idx;
}

double query(int l,int r,int x,int pos){ 
        
    if(l==r) return cal(tag[x],l);
    double ans=cal(tag[x],pos);
    int mid=l+r>>1;
    if(pos<=mid) ans=max(ans,query(l,mid,x<<1,pos));
    else ans=max(ans,query(mid+1,r,x<<1|1,pos));
    return ans;
}

int main(){ 
        
    scanf("%d",&q);
    while(q--){ 
        
        scanf("%s",ch);
        if(ch[0]=='P'){ 
        
            tot++;
            scanf("%lf %lf",&b[tot],&k[tot]);
            modify(1,n,1,tot);
        }
        else{ 
        
            int x;
            scanf("%d",&x);
            if(!tot){ 
        
                puts("0");continue;
            }
            printf("%d\n",(int)query(1,n,1,x)/100);
        }
    }
    return 0;
}

P4097 - [HEOI2013]Segment

要求在平面直角坐标系下维护两个操作:

  • 1. 1. 1. 在平面上加入一条线段。记第 i i i 条被插入的线段的标号为 i i i。
  • 2. 2. 2. 给定一个数 k k k,询问与直线 x = k x=k x=k 相交的线段中,交点纵坐标最大的线段的编号

:这题和之前插入的稍微有点区别,这道题是,相当于一条只在指定区间存在的直线,我们只需要按照线段树区间修改框架,只在当前区间属于 [ L , R ] [L,R] [L,R] 内放入线段即可。 [ L , R ] [L,R] [L,R] 在线段树上至多对应 l o g n logn logn 个区间,每次进入一个区间维护优势线段复杂度也是 O ( l o g n ) O(logn) O(logn),总复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

code:

const int maxn=1e5+10;
const int n=39989;
const int p=1e9;


int q,op,lastans,L,R;
int tot;
double k[maxn],b[maxn];
int tag[maxn<<2];
bool f;

inline double cal(int i,int x){ 
        
    return k[i]*x+b[i];
}

void modify(int l,int r,int x,int idx){ 
        
    if(L<=l&&r<=R){ 
        
        if(l==r){ 
        
            if(cal(tag[x],l)<cal(idx,l)) tag[x]=idx;
            return;
        }
        if(!tag[x]){ 
        tag[x]=idx;return;}
        int mid=l+r>>1;
        double y1=cal(tag[x],mid),y2=cal(idx,mid);
        if(k[tag[x]]<k[idx]){ 
        
            if(y1<=y2) modify(l,mid,x<<1,tag[x]),tag[x]=idx;
            else modify(mid+1,r,x<<1|1,idx);
        }
        else if(k[tag[x]]>k[idx]){ 
        
            if(y1<=y2) modify(mid+1,r,x<<1|1,tag[x]),tag[x]=idx;
            else modify(l,mid,x<<1,idx);
        }
        else if(b[tag[x]]<b[idx]) tag[x]=idx;
        return;
    }
    int mid=l+r>>1;
    if(L<=mid) modify(l,mid,x<<1,idx);
    if(mid<R) modify(mid+1,r,x<<1|1,idx);
}

pii query(int l,int r,int x,int pos){ 
        
    if(l==r){ 
        
        return pii(cal(tag[x],l),tag[x]);
    }
    pii ans=pii(cal(tag[x],pos),tag[x]),res;
    int mid=l+r>>1;
    if(pos<=mid) res=query(l,mid,x<<1,pos);
    else res=query(mid+1,r,x<<1|1,pos);
    if(ans.first>res.first) return ans;
    else if(ans.first<res.first) return res;
    else if(ans.second<res.second) return ans;
    return res;
}


int main(){ 
        
    scanf("%d",&q);
    while(q--){ 
        
        scanf("%d",&op);
        if(op==1){ 
        
            int x0,y0,x1,y1;
            scanf("%d %d %d %d",&x0,&y0,&x1,&y1);
            x0=(x0+lastans-1)%n+1;
            x1=(x1+lastans-1)%n+1;
            y0=(y0+lastans-1)%p+1;
            y1=(y1+lastans-1)%p+1;
            if(x0>x1) swap(x0,x1),swap(y0,y1);
            L=x0,R=x1;
            k[++tot]=(y1-y0)*1.0/(x1-x0);
            b[tot]=1.0*y0-k[tot]*x0;
            //cout<<"!"<<x0<<" "<<x1<<" "<<y0<<" "<<y1<<endl;
            modify(1,40000,1,tot);
        }
        else{ 
        
            int x;
            scanf("%d",&x);
            x=(x+lastans-1)%n+1;
            lastans=query(1,40000,1,x).second;
            printf("%d\n",lastans);
        }
    }
    return 0;
}

P4069 - [SDOI2016]游戏

给出一棵 n n n 个节点的树,且每条边存在一个边权,定义两个点的距离为两点之间的边权之和,现在要求支持两种操作:

  • 1. 1. 1.选择一条 s s s 到 t t t 的路径,对于路径上的一个点 r r r,若 r r r 与 s s s 的距离是 d i s dis dis,那么在点 r r r 上添加的数字是 a × d i s + b a \times dis + b a×dis+b
  • 2. 2. 2.选择一条 s s s 到 t t t 的路径,需要在路径上选一个点,在再这个点中选一个数字,要求数字越小越好

  • 首先发现 a × d i s + b a \times dis + b a×dis+b 这个式子形如一个一次函数,但是显然我们没法在树上套这个式子,我们来分析这个式子: y = a ∗ d i s t ( s , x ) + b y=a *dist(s,x) + b y=a∗dist(s,x)+b 设 w = l c a ( s , t ) w=lca(s,t) w=lca(s,t),那么 s − > w s->w s−>w 路径上的一次函数为: y = a ∗ ( d i s [ s ] − d i s [ i ] ) + b = − a ∗ d i s [ i ] + a ∗ d i s [ s ] + b y=a * (dis[s]-dis[i])+b=-a*dis[i]+a*dis[s]+b y=a∗(dis[s]−dis[i])+b=−a∗dis[i]+a∗dis[s]+b w − > t w->t w−>t 路径上的一次函数为: y = a ∗ ( d e p [ i ] − d e p [ w ] + d e p [ s ] − d e p [ w ] ) + b = a ∗ d e p [ i ] + a ∗ ( d e p [ s ] − 2 ∗ d e p [ w ] ) + b y=a*(dep[i]-dep[w]+dep[s]-dep[w])+b=a*dep[i]+a*(dep[s]-2*dep[w])+b y=a∗(dep[i]−dep[w]+dep[s]−dep[w])+b=a∗dep[i]+a∗(dep[s]−2∗dep[w])+b 所以我们分别维护参数为 k = − a , b = a ∗ d i s [ s ] + b k=-a,b=a*dis[s]+b k=−a,b=a∗dis[s]+b 与 k = a , b = a ∗ ( d e p [ s ] − 2 ∗ d e p [ w ] ) + b k=a,b=a*(dep[s]-2*dep[w])+b k=a,b=a∗(dep[s]−2∗dep[w])+b 的两个一次函数在指定区间的区间最小值即可

  • 之前都是求,这题要求,如果我们像之前一样查询,复杂度显然是 O ( n 2 l o g n ) O(n^2logn) O(n2logn) 难以承受。因此我们根据一次函数的单调性,再维护一个 m i [ x ] mi[x] mi[x] 代表当前区间中所有直线与 x = [ l . . r ] x=[l..r] x=[l..r] 相交的最小值,那么每次我们在当前区间或者子区间插入线段,我们显然要用线段的两个端点来更新最值,并且也要和传统线段树一样用子区间的信息更新。

code:

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int maxn=1e5+10;

int head[maxn],to[maxn<<1],nex[maxn<<1],w[maxn<<1],tot=0;
int sz[maxn],dep[maxn],son[maxn],f[maxn];
ll dis[maxn];
int dfn[maxn],top[maxn],ver[maxn],cnt=0;

ll mi[maxn<<2],tag[maxn<<2];
ll k[maxn<<2],b[maxn<<2];
int ktot=0,L,R;
int n,m;

inline void add(int u,int v,int d){ 
        
    to[++tot]=v;
    w[tot]=d;
    nex[tot]=head[u];
    head[u]=tot;
}

void dfs_1(int u,int fa){ 
        
    sz[u]=1;
    for(int i=head[u];i;i=nex[i]){ 
        
        int v=to[i];
        if(v==fa) continue;
        f[v]=u;
        dep[v]=dep[u]+1;
        dis[v]=dis[u]+w[i];
        dfs_1(v,u);
        sz[u]+=sz[v];
        if(sz[v]>sz[son[u]]) son[u]=v;
    }
}

void dfs_2(int u,int topf){ 
        
    top[u]=topf;
    dfn[u]=++cnt;
    ver[cnt]=u;
    if(son[u]) dfs_2(son[u],topf);
    for(int i=head[u];i;i=nex[i]){ 
        
        int v=to[i];
        if(v==f[u]||v==son[u]) continue;
        dfs_2(v,v);
    } 
}

ll val(int i,ll x){ 
        return k[i]*dis[ver[x]]+b[i];}

void modify(int l,int r,int x,int i){ 
        
    int mid=l+r>>1,lc=x<<1,rc=lc|1;
    if(L<=l&&r<=R){ 
        
        if(l==r){ 
        
            if(val(tag[x],l)>val(i,l)) tag[x]=i,mi[x]=val(i,l);
            return;
        }
        if(val(tag[x],l)<=val(i,l)&&val(tag[x],r)<=val(i,r)) return;
        ll y1=val(tag[x],mid),y2=val(i,mid);
        if(k[tag[x]]>k[i]){ 
        
            if(y1<=y2) modify(mid+1,r,rc,i);
            else modify(l,mid,lc,tag[x]),tag[x]=i;
        }
        else if(k[tag[x]]<k[i]){ 
        
            if(y1<=y2) modify(l,mid,lc,i);
            else modify(mid+1,r,rc,tag[x]),tag[x]=i;
        }
        else if(b[tag[x]]>b[i]) tag[x]=i;
        mi[x]=min(mi[x],min(val(tag[x],l),val(tag[x],r)));
        mi[x]=min(mi[x],min(mi[lc],mi[rc]));
        return;
    }
    if(L<=mid) modify(l,mid,lc,i);
    if(mid<R) modify(mid+1,r,rc,i);
    mi[x]=min(mi[x],min(mi[lc],mi[rc]));
}

ll query(int l,int r,int x){ 
        
    if(L<=l&&r<=R) return mi[x];
    ll res=min(val(tag[x],max(l,L)),val(tag[x],min(r,R)));
    int mid=l+r>>1;
    if(L<=mid) res=min(res,query(l,mid,x<<1));
    if(mid<R) res=min(res,query(mid+1,r,x<<1|1));
    return res;
}

inline int lca(int u,int v){ 
        
    while(top[u]!=top[v]){ 
        
        if(dep[top[u]]<dep[top[v]]) swap(u,v);
        u=f[top[u]];
    }
    return dep[u]>dep[v]?v:u;
}

inline void modify(int u,int w){ 
        
    while(top[u]!=top[w]){ 
        
        L=dfn[top[u]],R=dfn[u];
        modify(1,n,1,ktot);
        u=f[top[u]];
    }
    L=dfn[w],R=dfn[u];
    modify(1,n,1,ktot);
}

inline ll query(int u,int v){ 
        
    ll res=1e18;
    while(top[u]!=top[v]){ 
        
        if(dep[top[u]]<dep[top[v]]) swap(u,v);
        L=dfn[top[u]],R=dfn[u];
        res=min(res,query(1,n,1));
        u=f[top[u]];
    }
    if(dep[u]<dep[v]) swap(u,v);
    L=dfn[v],R=dfn[u];
    return min(res,query(1,n,1));
}

int main(){ 
        
    b[0]=123456789123456789;
    scanf("%d %d",&n,&m);
    int u,v,d;
    for(int i=1;i<n;i++){ 
        
        scanf("%d %d %d",&u,&v,&d);
        add(u,v,d),add(v,u,d);
    }
    for(int i=1;i<=(n<<2);i++) mi[i]=123456789123456789;
    dfs_1(1,0);
    dfs_2(1,1);
    int op,s,t;
    ll a,c;
    while(m--){ 
        
        scanf("%d %d %d",&op,&s,&t);
        if(op==1){ 
        
            scanf("%lld %lld",&a,&c);
            int w=lca(s,t);
            k[++ktot]=-a,b[ktot]=c+a*dis[s];
            modify(s,w);
            k[++ktot]=a,b[ktot]=a*(dis[s]-2*dis[w])+c;
            modify(t,w);
        }
        else printf("%lld\n",query(s,t));
    }
    return 0;
}

标签: 旧线网套连接器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

 锐单商城 - 一站式电子元器件采购平台  

 深圳锐单电子有限公司