线段树合并

有的时候我们需要将两棵线段树合并起来得到问题的答案,比如有的树上问题需要把一个点和所有儿子节点上的线段树合并起来得到这个点的答案,这时候就需要线段树合并这个算法了。

代码和 \(FHQ\_Treap\) 合并操作很像

int merge(int l,int r,int x,int y){
	if(!x||!y)return x^y;
	if(l==r)return /*合并叶子 x,y */,x;
	int mid=(l+r)>>1;
	ls[x]=merge(l,mid,ls[x],ls[y]);
	rs[x]=merge(mid+1,r,rs[x],rs[y]);
	//合并左右结点到 x 的贡献
	return x;
}

时间复杂度很显然就是原来两棵线段树重合部分。

模板题P4556

在每一个点开一个值域线段树记录每种救济粮的出现次数和出现最多的种类,把书上路径添加救济粮转化为树上差分,每次操作就是一次单点修改,最后再深搜一变合并答案即可。答案在每次合并完一个点的所有子节点后直接统计,要不然还要可持久化保留当前的树,太占空间了。

为什么这样时间复杂度是对的呢?因为操作数是 \(m\) 次,我们单点修改次数数量级也是 \(m\) ,最多有 \(mlogm\) 个结点,所以合并起来重复部分不可能超过 \(mlogm\) ,所以复杂度线性对数

代码:

#include<iostream>
#include<cstring>
#include<vector>
#define int long long
#define inf 0x3f3f3f3f
#define llinf 0x3f3f3f3f3f3f3f3fll
const int N = 1e5+5;
//缺省源略
using namespace nazuna_;
int n,m;
vector<int>g[N];
int mi[20][N],lg[N],dfn[N],cnt;
int R[N],ls[N*64],rs[N*64],nd;
struct data{
	int mx,sz;
	data operator + (data &y){
		if(y.sz>sz)return y;
		if(sz>y.sz)return *this;
		if(mx>y.mx)return y;
		return *this;
	}
}tag[N*64];
void modify(int &u,int l,int r,int p,int v){
	if(!u)u=++nd;
	if(l==r)return tag[u].sz+=v,tag[u].mx=p,void();
	int mid=(l+r)>>1;
	if(p<=mid)modify(ls[u],l,mid,p,v);
	else modify(rs[u],mid+1,r,p,v);
	tag[u]=tag[ls[u]]+tag[rs[u]];
}
int merge(int l,int r,int x,int y){
	if(!x||!y)return x^y;
	if(l==r)return tag[x].sz+=tag[y].sz,x;
	int mid=(l+r)>>1;
	ls[x]=merge(l,mid,ls[x],ls[y]);
	rs[x]=merge(mid+1,r,rs[x],rs[y]);
	tag[x]=tag[ls[x]]+tag[rs[x]];
	return x;
}
int get(int x,int y){return dfn[x]<dfn[y]?x:y;}
void dfs(int u,int f){
	mi[0][dfn[u]=++cnt]=f;
	for(int v:g[u])if(v!=f)dfs(v,u);
}
int lca(int x,int y){
	if(x==y)return x;
	if((x=dfn[x])>(y=dfn[y]))swap(x,y);
	int ap=lg[y - x++];
	return get(mi[ap][x],mi[ap][y-(1<<ap)+1]);
}
int ans[N];
void dfs2(int u,int f){
	for(int v:g[u]){
		if(v!=f){
			dfs2(v,u),R[u]=merge(1,1e5,R[u],R[v]);
		}
	}
	ans[u]=tag[R[u]].mx;
}
signed main(){
	n=rd(),m=rd();
	for(int i=2;i<=n;i++){
		lg[i]=lg[i/2]+1;
		int u=rd(),v=rd();
		g[u].emplace_back(v),g[v].emplace_back(u);
	}
	dfs(1,0);
	for(int ap=1;ap<=lg[n];ap++)
		for(int i=1;i+(1<<ap)-1<=n;i++)
			mi[ap][i]=get(mi[ap-1][i],mi[ap-1][i+(1<<(ap-1))]);
	while(m--){
		int u=rd(),v=rd(),z=rd();int d=lca(u,v);
		modify(R[u],1,1e5,z,1);
		modify(R[v],1,1e5,z,1);
		modify(R[d],1,1e5,z,-1);
		if(d!=1)modify(R[mi[0][dfn[lca(u,v)]]],1,1e5,z,-1);
	}
	dfs2(1,0);
	for(int i=1;i<=n;i++)print(ans[i]),ps;
	return 0;
}

P3224 [HNOI2012] 永无乡

平衡树也能做,但线段树合并更好写且常数小,不过空间复杂度线性对数的,回头有机会用平衡树写一次。

考虑用并查集维护连通块,每个连通块开一个权值线段树(显然要动态开点),合并连通块时使用线段树合并即可

code

posted @ 2026-05-31 08:28  Sayhere  阅读(2)  评论(0)    收藏  举报