对于值域线段树,如果他们维护相同的值域,那么他们对各个子区间的划分是一致的,于是暗示着我们这些线段树是具有可合并性的。

我们可以使用两个指针以递归形式同步遍历这两棵线段树,也就是他们始终指向同一个区间。

显然地,会有两种情况:

1,其中有一个是空的,那么我们取另一个不空的作为新线段树的节点
2,两个都不为空,那么递归合并这两棵线段树的左右子树,最后删除一个,留下另一个,自底向上更新信息。

常见的写法有两种,它们各有利弊:

第一种,我们直接把两个合起来,也就是说,其中一个的结构会被破坏,这也就意味着如果合并后还有操作针对于被合并了的的某棵线段树那么我们就GG,于是只能离线去搞。

但是优点就是省空间。

int merge(int p,int q,int l,int r){
if(!p||!q)return p+q;
if(l==r){
dat[p]+=dat[q];
t[p]=l;
return p;
}

int mid=(l+r)>>1;
L[p]=merge(L[p],L[q],l,mid);
R[p]=merge(R[p],R[q],mid+1,r);

push_up(p);
return p;
}

第二种,我们可以开出一个新的节点来存放合并后的树,这样我们原来的线段树的结构不变就得到了保障,这样就可以在线了。

缺点自然也很显然,每次合并我们都要重开一个新的节点,于是空间是炸的。

int merge(int p,int q,int l,int r) {
if(!p||!q)return p+q;

int rt=++tot;
if(l==r){
dat[rt]=dat[p]+dat[q];
t[rt]=l;
return rt;
}

int mid=(x+y)>>1;
L[root]=merge(L[p],L[q],l,mid);
R[root]=merge(R[p],R[q],mid+1,r);

pushup(rt);
return rt;
}

根据情况权衡利弊以后谨慎使用吧

如果空间卡的死,又保证不对被合并过的树进行操作,那就选第一种。

要是要求在线保留树的结构,空间不严格那就第二种。

其他的部分对普通权值线段树微改一下就好了,没什么太大的区别;

复杂度是$O(mlogn)$的,很高效了

例题:P4556 [Vani有约会]雨天的尾巴 /【模板】线段树合并

题目很简单,我们直接把$z$当成值域,每一个节点维护一个权值线段树。

然后针对每次放粮我们直接树上差分,$u$到$v$的路径直接处理成$u+1$,$v+1$,$lca(u,v)-1$,$fa[lca(u,v)]-1$就好了,最后我们做一次树上前缀和就可了

//#define LawrenceSivan

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
#define INF 0x3f3f3f3f
#define re register
const int maxn=1e5+5;
const int maxm=6e6+5;

inline int mymax(int a,int b){
return a>b?a:b;
}

inline int mymin(int a,int b){
return a<b?a:b;
}

int n,m,MAX,tot;
int X[maxn],Y[maxn],Z[maxn],ans[maxn],root[maxn];

int L[maxm],R[maxm],dat[maxm],t[maxm];


int head[maxn],nxt[maxn<<1],to[maxn<<1],cnt;

inline void add(int u,int v){
nxt[++cnt]=head[u];
to[cnt]=v;
head[u]=cnt;
}

int fa[maxn],top[maxn],size[maxn],son[maxn],dep[maxn];

void dfs1(int u,int f){
size[u]=1;
fa[u]=f;
dep[u]=dep[f]+1;
for(re int i=head[u];i;i=nxt[i]){
int v=to[i];
if(v==f)continue;
dfs1(v,u);
size[u]+=size[v];
if(size[v]>size[son[u]]){
son[u]=v;
}
}
}

void dfs2(int u,int topf){
top[u]=topf;
if(!son[u])return;
dfs2(son[u],topf);
for(re int i=head[u];i;i=nxt[i]){
int v=to[i];
if(v==topf||v==son[u]||top[v])continue;
dfs2(v,v);
}
}

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

inline void push_up(int rt){
if(dat[L[rt]]>=dat[R[rt]])dat[rt]=dat[L[rt]],t[rt]=t[L[rt]];
else dat[rt]=dat[R[rt]],t[rt]=t[R[rt]];
}

int modify(int rt,int l,int r,int pos,int val){
if(!rt)rt=++tot;

if(l==r){
dat[rt]+=val;
t[rt]=l;
return rt;
}

int mid=(l+r)>>1;
if(pos<=mid)L[rt]=modify(L[rt],l,mid,pos,val);
else R[rt]=modify(R[rt],mid+1,r,pos,val);

push_up(rt);
return rt;
}

int merge(int p,int q,int l,int r){
if(!p||!q)return p+q;
if(l==r){
dat[p]+=dat[q];
t[p]=l;
return p;
}

int mid=(l+r)>>1;
L[p]=merge(L[p],L[q],l,mid);
R[p]=merge(R[p],R[q],mid+1,r);

push_up(p);
return p;
}

void solve(int u){
for(re int i=head[u];i;i=nxt[i]){
int v=to[i];
if(dep[v]>dep[u])solve(v),root[u]=merge(root[u],root[v],1,MAX);
if(dat[root[u]])ans[u]=t[root[u]];
}
}

inline int read(){
int x=0, f=1;char ch=getchar();
while (!isdigit(ch)) {if(ch=='-')f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+(ch^48);ch=getchar();}
return x*f;
}

int main() {
#ifdef LawrenceSivan
freopen("aa.in", "r", stdin);
freopen("aa.out", "w", stdout);
#endif
n=read();m=read();
for(re int i=1,u,v;i<n;i++){
u=read();v=read();
add(u,v);
add(v,u);
}

dfs1(1,0);
dfs2(1,1);

for(re int i=1;i<=m;i++){
X[i]=read();Y[i]=read();Z[i]=read();
MAX=mymax(Z[i],MAX);
}

for(re int i=1;i<=m;i++){
int LCA=lca(X[i],Y[i]);
root[X[i]]=modify(root[X[i]],1,MAX,Z[i],1);
root[Y[i]]=modify(root[Y[i]],1,MAX,Z[i],1);
root[LCA]=modify(root[LCA],1,MAX,Z[i],-1);
if(fa[LCA])root[fa[LCA]]=modify(root[fa[LCA]],1,MAX,Z[i],-1);
}

solve(1);

for(re int i=1;i<=n;i++){
printf("%d\n",ans[i]);
}

return 0;
}