線段樹合併

前置知識:
倍增LCA

樹上差分

可持久化線段樹(動態開點)

定義:

線段樹合併就是合併值域相同的線段樹。

線段樹合併

 

非常的好理解。Code:

 1 int merge(int p,int q,int l,int r){//在這裡,我們選擇將兩棵樹合併到第一棵樹中
 2     if(!q) return p;
 3     if(!p) return q;//因為使用動態開點線段樹,所以當其中一棵線段樹無該節點是便可停止
 4     if(l==r){
 5         t[p].maxn+=t[q].maxn;
 6         if(t[p].maxn>0) t[p].pos=l;
 7         else t[p].pos=0;
 8         return p;
 9     }
10     int mid=(l+r)/2;
11     t[p].l=merge(t[p].l,t[q].l,l,mid);
12     t[p].r=merge(t[p].r,t[q].r,mid+1,r);
13     pushup(p);
14     return p;
15 }

懂不起?那確實。

讓我們來看一道例題來感受線段樹合併的用途。

P4556 [Vani有約會]雨天的尾巴 /【模板】線段樹合併

思路:

對於每個節點,我們用動態開點線段樹來記錄每種救濟糧的個數。

那這道題和線段樹合併有什麼關係呢?

要合併,就需要在統計答案時進行「」的操作。

於是我們就想到了差分

樹+差分=樹上差分

於是思路就出來了:

用樹上差分維護n棵線段樹,統計答案時利用線段樹合併完成。

詳細思路在代碼中解釋:

 

  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 const int N=1e5+5;
  4 const int M=6e6+5; 
  5 int n,m; 
  6 int a,b;
  7 int x,y,z;
  8 int T;
  9 int idx=0,tot=0;
 10 int f[N][30],d[N],root[N],head[N];
 11 int ans[N];
 12 struct node{
 13     int v,next;
 14 }s[2*N];
 15 struct node1{
 16     int l,r,maxn,pos;
 17     //maxn用於記錄該區間最多的飼料數
 18     //pos用於記錄該區間最多的飼料的種類 
 19 }t[M];
 20 
 21 void build(int a,int b){//建圖 
 22     s[++idx].v=b;
 23     s[idx].next=head[a];
 24     head[a]=idx;
 25 }
 26 
 27 void bfs(){//倍增求LCA的預處理 
 28     memset(d,0,sizeof(d));
 29     d[1]=1;
 30     queue<int> q;
 31     q.push(1);
 32     while(!q.empty()){
 33         int x=q.front();
 34         q.pop();
 35         for(int i=head[x];i;i=s[i].next){
 36             int y=s[i].v;
 37             if(d[y]) continue;
 38             d[y]=d[x]+1;
 39             f[y][0]=x;
 40             for(int i=1;i<=T;i++) f[y][i]=f[f[y][i-1]][i-1];
 41             q.push(y); 
 42         }
 43     }
 44     return ;
 45 }
 46 
 47 int LCA(int x,int y){//求LCA 
 48     if(d[x]<d[y]) swap(x,y);
 49     for(int i=T;i>=0;i--){
 50         if(d[f[x][i]]>=d[y]) x=f[x][i];
 51     }
 52     if(x==y) return x;
 53     for(int i=T;i>=0;i--){
 54         if(f[x][i]!=f[y][i]){
 55             x=f[x][i];
 56             y=f[y][i];
 57         }
 58     }
 59     return f[x][0];
 60 }
 61 
 62 int Build(){//動態開點 
 63     tot++;
 64     t[tot].l=t[tot].r=t[tot].maxn=t[tot].pos=0;
 65     return tot;
 66 }
 67 
 68 void pushup(int p){//更新最大飼料數量及種類 
 69     if(t[t[p].l].maxn>=t[t[p].r].maxn){//注意:因為數量相同時,編號小的優先,所以是>=而不是> 
 70         t[p].maxn=t[t[p].l].maxn;
 71         t[p].pos=t[t[p].l].pos;
 72     }
 73     else{
 74         t[p].maxn=t[t[p].r].maxn;
 75         t[p].pos=t[t[p].r].pos;
 76     }
 77     return ;
 78 }
 79 
 80 void modify(int p,int l,int r,int x,int v){//單點修改 
 81     if(l==r){//因為用了樹上差分,所以區間和可能是負數 
 82         t[p].maxn+=v;
 83         if(t[p].maxn>0) t[p].pos=x;
 84         else t[p].pos=0;
 85         return ;
 86     }
 87     int mid=(l+r)/2;
 88     if(x<=mid){
 89         if(!t[p].l) t[p].l=Build();//記住!動態開點! 
 90         modify(t[p].l,l,mid,x,v);
 91     }
 92     else{
 93         if(!t[p].r) t[p].r=Build();//記住!動態開點!
 94         modify(t[p].r,mid+1,r,x,v);
 95     }
 96     pushup(p);
 97     return ;
 98 }
 99 
100 int merge(int p,int q,int l,int r){//線段樹合併 
101     if(!q) return p;
102     if(!p) return q;
103     if(l==r){
104         t[p].maxn+=t[q].maxn;
105         if(t[p].maxn>0) t[p].pos=l;
106         else t[p].pos=0;
107         return p;
108     }
109     int mid=(l+r)/2;
110     t[p].l=merge(t[p].l,t[q].l,l,mid);
111     t[p].r=merge(t[p].r,t[q].r,mid+1,r);
112     pushup(p);
113     return p;
114 }
115 
116 void dfs(int x){
117     if(!root[x]) root[x]=Build();//記住!動態開點!
118     for(int i=head[x];i;i=s[i].next){
119         int y=s[i].v;
120         if(f[x][0]==y) continue; 
121         dfs(y);
122         root[x]=merge(root[x],root[y],1,N);//注意:此處為root[x]而非x是因為對於原樹的每一個節點都是線段樹的根節點 
123     }
124     ans[x]=t[root[x]].pos;
125     return ;
126 } 
127 
128 int main(){
129     cin>>n>>m;
130     T=(log(n)/log(2))+1;
131     for(int i=1;i<n;i++){
132         cin>>a>>b;
133         build(a,b);
134         build(b,a);
135     }
136     bfs();//預處理 
137     for(int i=1;i<=m;i++){
138         cin>>x>>y>>z;
139         int lc=LCA(x,y);
140         if(!root[x]) root[x]=Build();
141         modify(root[x],1,N,z,1);
142         if(!root[y]) root[y]=Build();
143         modify(root[y],1,N,z,1);
144         if(!root[lc]) root[lc]=Build();
145         modify(root[lc],1,N,z,-1);
146         if(!root[f[lc][0]]) root[f[lc][0]]=Build();
147         modify(root[f[lc][0]],1,N,z,-1);//樹上差分 
148     }
149     dfs(1);//答案 
150     for(int i=1;i<=n;i++) cout<<ans[i]<<endl;
151     return 0;
152 } 

 

這樣複雜度是多少呢?

由於是動態開點線段樹,所以基於總操作數m,每次操作至多新建一條長為log v的鏈,所以至多只有O(m log v) 的節點。

那麼合併時,遍歷了線段樹中所有已有的節點。

那麼這樣合併複雜度也是O(mlogv)