今天来谈谈李超线段树

例题:bzoj1568
那么这就是在二维平面里维护直线,资磁插入一条直线和查询直线x=k与其他直线相交的点中,最大的y坐标。

那么什么是李超线段树呢?
对于一个区间,我们维护该区间的所有直线中,从上往下去看可以看到它的长度最大的一条直线(即没有被其他直线覆盖的长度最大)
现在插入一条直线(称为当前直线)到了一个区间。
1.如果还没有记录直线,则将当前直线变成记录直线,返回。
2.如果当前直线完全被记录直线覆盖,直接返回。
3.如果当前直线完全覆盖记录直线,将当前直线变成记录直线,返回。
4.如果当前直线和记录直线有交点,则求出交点的x坐标,根据x坐标和当前区间的中点的大小等关系,判断当前直线和记录直线中哪个的未被覆盖长度长一些,长一些的叫l1,短一些的叫l2
记录直线变为l1,继续递归处理l2

复杂度证明:由于第4步操作中,我们要递归的区间每次减小了至少一半。所以是 O ( l o g n ) 的。注意我们复杂度的关键是让继续递归的区间减小一半,所以假设该题的x坐标要离散化,对于线段树上位置i,实际对应的x是 x i ,那么我们应该让交点x坐标和当前区间的 x l + r 2 作比较,而非与 x l + x r 2 比较。

对于查询操作,我们在线段树上递归到查询的那个x,在过程中,每路过一个区间,都要把这个区间的记录直线拿来更新答案。这是标记永久化。

所以什么是李超线段树?用标记永久化维护平面内线段覆盖情况的线段树



#include<bits/stdc++.h>
using namespace std;
#define RI register int
typedef double db;
typedef long long LL;
const int n=50000,N=50005;
int Q,flag[N<<2];db b[N<<2],k[N<<2];
void insert(int s,int t,int i,db B,db K) {
    if(!flag[i]) {b[i]=B,k[i]=K,flag[i]=1;return;}
    int mid=(s+t)>>1;
    db l1=1.0*s*K+B,r1=1.0*t*K+B,l2=1.0*s*k[i]+b[i],r2=1.0*t*k[i]+b[i];
    if(l1<=l2&&r1<=r2) return;
    if(l1>l2&&r1>r2) {b[i]=B,k[i]=K;return;}
    db xx=(B-b[i])/(k[i]-K);
    if(l1>l2) {
        if(xx>mid) insert(mid+1,t,(i<<1)|1,b[i],k[i]),b[i]=B,k[i]=K;
        else insert(s,mid,i<<1,B,K);
    }
    else {
        if(xx>mid) insert(mid+1,t,(i<<1)|1,B,K);
        else insert(s,mid,i<<1,b[i],k[i]),b[i]=B,k[i]=K;
    }
}
db query(int x,int s,int t,int i) {
    if(s==t) return 1.0*x*k[i]+b[i];
    int mid=(s+t)>>1;db re=1.0*x*k[i]+b[i];
    if(x<=mid) re=max(re,query(x,s,mid,i<<1));
    else re=max(re,query(x,mid+1,t,(i<<1)|1));
    return re;
}
int main()
{
    char ch[10];int x;db B,K;
    scanf("%d",&Q);
    while(Q--) {
        scanf("%s",ch);
        if(ch[0]=='P') scanf("%lf%lf",&B,&K),insert(1,n,1,B-K,K);
        else {
            scanf("%d",&x);
            db ans=query(x,1,n,1);
            printf("%lld\n",(LL)(ans/100.0));
        }
    }
    return 0;
}

再看一道例题:bzoj4515
树链剖分+李超线段树
现在插入直线变成插入线段,那么要等线段树区间被插入的线段的x区间完全包含时,再执行那些比较当前区间未被覆盖长度最大的线段的操作,复杂度变成 O ( l o g 2 n )
等等…..加上树剖的复杂度, O ( n l o g 3 n ) 10 5 ,1s……
所以要相信树剖和李超线段树的常数都是很小的=。=
然后在李超线段树里维护一个 m i ,表示区间最小值,计算mi的时候,要算线段树上左右区间mi的贡献和当前区间的记录线段的贡献。



#include<bits/stdc++.h>
using namespace std;
#define RI register int
typedef long long LL;
typedef double db;
LL read() {
    LL q=0,w=1;char ch=' ';
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') w=-1,ch=getchar();
    while(ch>='0'&&ch<='9') q=q*10+(LL)(ch-'0'),ch=getchar();
    return q*w;
}
const int N=100005;
const LL inf=123456789123456789;
int n,m,tot,tim;
int h[N],ne[N<<1],to[N<<1],sz[N],top[N],fa[N],dep[N],pos[N],repos[N];
LL w[N<<1],dis[N];
void add(int x,int y,LL z) {to[++tot]=y,ne[tot]=h[x],h[x]=tot,w[tot]=z;}
void dfs1(int x,int las) {
    sz[x]=1,dep[x]=dep[las]+1,fa[x]=las;
    for(RI i=h[x];i;i=ne[i])
        if(to[i]!=las) dis[to[i]]=dis[x]+w[i],dfs1(to[i],x),sz[x]+=sz[to[i]];
}
void dfs2(int x,int las) {
    int mx=0,bj=0;pos[x]=++tim,repos[tim]=x;
    for(RI i=h[x];i;i=ne[i])
        if(to[i]!=las&&sz[to[i]]>mx) mx=sz[to[i]],bj=to[i];
    if(!bj) return;
    top[bj]=top[x],dfs2(bj,x);
    for(RI i=h[x];i;i=ne[i])
        if(to[i]!=las&&bj!=to[i]) top[to[i]]=to[i],dfs2(to[i],x);
}

LL k[N<<2],b[N<<2],mi[N<<2];int flag[N<<2];
void build(int s,int t,int i) {
    mi[i]=inf;
    if(s==t) return;
    int mid=(s+t)>>1;
    build(s,mid,i<<1),build(mid+1,t,(i<<1)|1);
}
void up(int i) {mi[i]=min(min(mi[i],mi[i<<1]),mi[(i<<1)|1]);}
void insert(int l,int r,int s,int t,int i,LL K,LL B) {
    if(l<=s&&t<=r) {
        int mid=(s+t)>>1;
        LL dl=dis[repos[s]],dr=dis[repos[t]],dm=dis[repos[mid]];
        LL l1=K*dl+B,r1=K*dr+B,l2=k[i]*dl+b[i],r2=k[i]*dr+b[i];
        if(!flag[i]) {
            k[i]=K,b[i]=B,flag[i]=1,mi[i]=min(mi[i],min(l1,r1));
            return;
        }
        if(l1>=l2&&r1>=r2) return;
        if(l1<l2&&r1<r2) {
            k[i]=K,b[i]=B,mi[i]=min(mi[i],min(l1,r1));
            return;
        }
        db xx=(db)(b[i]-B)/(db)(K-k[i]);
        if(l1<l2) {
            if(xx<(db)dm) insert(l,r,s,mid,i<<1,K,B);
            else insert(l,r,mid+1,t,(i<<1)|1,k[i],b[i]),k[i]=K,b[i]=B;
        }
        else {
            if(xx<(db)dm) insert(l,r,s,mid,i<<1,k[i],b[i]),k[i]=K,b[i]=B;
            else insert(l,r,mid+1,t,(i<<1)|1,K,B);
        }
        mi[i]=min(mi[i],min(l1,r1)),up(i);
        return;
    }
    int mid=(s+t)>>1;
    if(l<=mid) insert(l,r,s,mid,i<<1,K,B);
    if(mid+1<=r) insert(l,r,mid+1,t,(i<<1)|1,K,B);
    up(i);
}
LL query(int l,int r,int s,int t,int i) {
    if(l<=s&&t<=r) return mi[i];
    int mid=(s+t)>>1;LL re=inf;
    if(flag[i]) {
        LL dl=dis[repos[max(l,s)]];
        LL dr=dis[repos[min(r,t)]];
        re=min(k[i]*dl+b[i],k[i]*dr+b[i]);
    }
    if(l<=mid) re=min(re,query(l,r,s,mid,i<<1));
    if(mid+1<=r) re=min(re,query(l,r,mid+1,t,(i<<1)|1));
    return re;
}

int lca(int x,int y) {
    while(top[x]!=top[y]) {
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        x=fa[top[x]];
    }
    return dep[x]<dep[y]?x:y;
}
void walk(int x,int o,LL K,LL B) {
    while(top[x]!=top[o]) {
        insert(pos[top[x]],pos[x],1,n,1,K,B);
        x=fa[top[x]];
    }
    insert(pos[o],pos[x],1,n,1,K,B);
}
LL getans(int x,int y) {
    LL re=inf;
    while(top[x]!=top[y]) {
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        re=min(re,query(pos[top[x]],pos[x],1,n,1));
        x=fa[top[x]];
    }
    if(pos[x]>pos[y]) swap(x,y);
    re=min(re,query(pos[x],pos[y],1,n,1));
    return re;
}
int main()
{
    int x,y,bj;LL z,A,B;
    n=read(),m=read();
    for(RI i=1;i<n;++i)
        x=read(),y=read(),z=read(),add(x,y,z),add(y,x,z);
    dfs1(1,0),top[1]=1,dfs2(1,0);
    build(1,n,1);
    while(m--) {
        bj=read(),x=read(),y=read();
        int o=lca(x,y);
        if(bj==1) {
            A=read(),B=read();
            walk(x,o,-A,A*dis[x]+B);
            walk(y,o,A,A*(dis[x]-2LL*dis[o])+B);
        }
        else printf("%lld\n",getans(x,y));
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/litble/article/details/81234303