zoukankan      html  css  js  c++  java
  • [线段树系列] 线段树合并

    这一篇来讲讲线段树合并。

    前置知识:动态开点线段树

    还是一样先给一道例题:[JOI2012] Building2

    题面是日文的,这里给出中文翻译:

    n个城市,它们组成了一棵树。 第i个城市有一座高度为Hi的建筑。

    你需要选择一条尽量长路径,设路径中有k个点, 依次分别为i1,i2,ik1,ik,使得路径满足Hi1<Hi2<<Hik,k个点不一定要连续,求k的最大值。

    概述:求树上LIS( 最长上升子序列 ),不会求LIS也没有关系,这只是一道例题。

    先来考虑朴素做法:

    取出树上的所有路径,对它们分别求LIS。

    可以做,但是复杂度太高,不可行。

    我们发散思维,容易想到一种做法:

    对于每一个点x,我们发现经过它的一条路径的LIS可以由两部分组成。

    以x开头,x左边的部分路径如果是下降的,那右边就要是上升的( 值h )。

    以x开头,x左边如果是上升的,右边就要是下降的。

    画个图方便理解:

    图中蓝色的显然就是过x的LIS

    根据上面的想法,我们求出以x开头,往左的LIS=3,往右的LDS( 最长下降子序列 )=2。

    以x开头,往左的LDS=1,往右的LIS=2。

    我们比较两个结果( 相加-1,要把自己重复算的那一次减去 ),4和2,显然答案就是4。

    现在考虑怎么实现这个想法,

    刚开始,所有点的LIS和LDS都是1( 自己 )。

    然后我们考虑从底向上合并答案。

    dfs遍历到底,开始向上回溯,每次询问找到左右子树,直接合并答案。

    如图:

    还是一样,理解一下就好。看不懂没关系,可以照着代码自己画几组数据看看合并过程。

    我们用动态开点线段树维护一下这个合并,题就做出来了。

    现在来讲线段树合并。

    顾名思义,线段树合并就是把两棵线段树合并成一棵。

    可以在合并的过程中更新信息。

    线段树合并可以解决大部分更新答案需要合并的问题,dsu( 树上启发式合并 )能做的它也基本可以。

    给出合并的代码,以上面的例题为例:

    inline void Merge(int&x,int y){
        if(!x||!y){
            x=x+y;return;
        }
        lis[x]=_fmax(lis[x],lis[y]);
        lds[x]=_fmax(lds[x],lds[y]);
        ret=fmax(ret,_fmax(lis[lc[x]]+lds[rc[y]],lds[rc[x]]+lis[lc[y]]));
        Merge(lc[x],lc[y]);
        Merge(rc[x],rc[y]);
    }

    可以先学学可并堆(左偏树),线段树合并和可并堆是有挺多相似的地方的。

    左偏树的博客在我写完线段树系列后大概会写一篇。毕竟日更博主...。

    然后我们的其他操作就和动态开点线段树基本一致了。

    但是节点值的更新,是每次修改都要更新,这一点不像动态开点线段树( 在叶子节点更新 )。

    inline void Update(int&x,int l,int r,int pos,int val,int *h){
        if(!x)x=++ncnt;
        h[x]=_fmax(h[x],val);
        if(l==r)return;
        int mid=(l+r)>>1;
        if(pos<=mid)Update(lc[x],l,mid,pos,val,h);
        else Update(rc[x],mid+1,r,pos,val,h);
    }

    现在给出我写的上面那道例题的标程。

    注释已经全部加好了,请放心食用。看不懂的地方可以在下方评论区里留言。

    #include<bits/stdc++.h>
    using namespace std;
    #define getchar gc
    char buf[1000010],*pos,*End;
    inline char gc(){
        if(pos==End){
            End=(pos=buf)+fread(buf,1,1000000,stdin);
            if(pos==End)return EOF;
        }return *pos++;
    }
    //fread高速读入
    const int N=100010;
    struct Edge{
        int u,v,nxt;
    }e[N<<1];
    int head[N],tot;
    inline void addedge(int u,int v){
      e[++tot].u=u;
      e[tot].v=v;
      e[tot].nxt=head[u];
      head[u]=tot;
    }//前向星存图
    inline int _fmax(int x,int y){
        return (((y-x)>>31)&(x^y))^y;
    }//快速max
    int ret;
    int root[N];
    int lc[N*20],rc[N*20],lis[N*20],lds[N*20],ncnt;//开nlogn个点
    inline void Merge(int&x,int y){//合并
        if(!x||!y){
            x=x+y;return;
        }
        lis[x]=_fmax(lis[x],lis[y]);
        lds[x]=_fmax(lds[x],lds[y]);//更新值
        ret=_fmax(ret,_fmax(lis[lc[x]]+lds[rc[y]],lds[rc[x]]+lis[lc[y]]));
        Merge(lc[x],lc[y]);
        Merge(rc[x],rc[y]);//合并
    }
    inline void Update(int&x,int l,int r,int pos,int val,int *h){
        if(!x)x=++ncnt;//开点
        h[x]=_fmax(h[x],val);//每个节点都更新值
        if(l==r)return;//到叶子节点就可以返回了
        int mid=(l+r)>>1;
        if(pos<=mid)Update(lc[x],l,mid,pos,val,h);
        else Update(rc[x],mid+1,r,pos,val,h);//递归更新值
    }
    inline int Query(int&x,int l,int r,int L,int R,int *h){
        if(l>r)return 0;
        if(!x)return 0;
        if(L<=l && r<=R)return h[x];//跟普通的动态开点线段树一样
        int ret=0,mid=(l+r)>>1;
        if(L<=mid)ret=_fmax(ret,Query(lc[x],l,mid,L,R,h));
        if(R>mid)ret=_fmax(ret,Query(rc[x],mid+1,r,L,R,h));
        return ret;
    }
    int d[N],kcnt;
    inline int bis(int x){
        return lower_bound(d+1,d+kcnt+1,x)-d;//离散化后查找值的二分
    }
    int n,val[N];
    int ans;
    inline void dfs(int x,int f){//dfs到底,回溯时进行合并
        for(int i=head[x];i;i=e[i].nxt){
            int y=e[i].v;
            if(y==f)continue; 
            dfs(y,x);
        }
        ret=0;
        int nlis=0,nlds=0,ilis,ilds;
        for(int i=head[x];i;i=e[i].nxt){
            int y=e[i].v;
            if(y==f)continue;
            ilis=Query(root[y],1,kcnt,1,val[x]-1,lis);//查找子树的lis
            ilds=Query(root[y],1,kcnt,val[x]+1,kcnt,lds);//查找子树的lds
            Merge(root[x],root[y]);//合并子树
            ans=_fmax(ans,ilis+1+nlds);
            ans=_fmax(ans,ilds+1+nlis);//更新答案
            nlis=_fmax(nlis,ilis);
            nlds=_fmax(nlds,ilds);//更新节点的值
        }
        ans=_fmax(ans,ret);//更新答案
        Update(root[x],1,kcnt,val[x],nlis+1,lis);
        Update(root[x],1,kcnt,val[x],nlds+1,lds);//更新线段树
    }
    inline int read(){
        int data=0,w=1;char ch=0;
        while(ch!='-' && (ch<'0'||ch>'9'))ch=getchar();
        if(ch=='-')w=-1,ch=getchar();
        while(ch>='0' && ch<='9')data=data*10+ch-'0',ch=getchar();
        return data*w;
    }
    int main(){
        n=read();
        for(int i=1;i<=n;i++){
            val[i]=read();
            d[++kcnt]=val[i];
        }
        sort(d+1,d+kcnt+1);
        kcnt=unique(d+1,d+kcnt+1)-d-1;//离散化
        for(int i=1;i<=n;i++)
            val[i]=bis(val[i]);
        int x,y;
        for(int i=1;i<n;i++){
            x=read();y=read();
            addedge(x,y);addedge(y,x);//加边
        }
        dfs(1,0);
        printf("%d
    ",ans);
        return 0;
    }

    总结一下,遇到需要合并更新答案的题,就可以用线段树合并解决。

    那么本篇到这里就结束了,撰文不易,希望帮到各位。

    下一篇更新线段树优化建图,本系列持续更新,求点赞求关注。

  • 相关阅读:
    2月16号
    2月15号
    dubbo与springmvc的简单使用
    dubbo与zookeeper学习中的问题
    linux下jdk与tomcat的安装与配置
    mysql存储引擎
    mysql存储过程
    mysql子查询与连接查询
    mysql简单增删改查(CRUD)
    SpringMvc执行流程
  • 原文地址:https://www.cnblogs.com/light-house/p/11759894.html
Copyright © 2011-2022 走看看