[線段樹系列] 線段樹合並


這一篇來講講線段樹合並。

前置知識:動態開點線段樹

還是一樣先給一道例題:[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\n",ans);
    return 0;
}

總結一下,遇到需要合並更新答案的題,就可以用線段樹合並解決。

那么本篇到這里就結束了,撰文不易,希望幫到各位。

下一篇更新線段樹優化建圖,本系列持續更新,求點贊求關注。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM