树链剖分 重链剖分

树链剖分/重链剖分

关于树上的查询修改问题,如果采用DFS的方式处理,时间效率会上升到 \(O(n^2)\)

可以将树转化为多个链,然后映射到线段树上维护,可以降低时间复杂度到 \(O(n\log n)\)


树链剖分就是将树切分为多个链,然后将这些链上的节点紧密地映射到线段树上

struct node{
	int l,r,sum,add;
};
node tr[4*N];
int n,m,r,mod;
int a[N],cor[N],idx;
vector<int> e[N];
int fa[N],son[N],top[N];
int sz[N],dep[N],id[N];

首先是在剖分树链前的预处理:

void dfs1(int x,int p){
	dep[x]=dep[p]+1;//处理树上每个节点的深度
	sz[x]=1;//初始化以x节点为根的子树的大小
	fa[x]=p;//记录x节点的父亲
	for (auto v:e[x]){
		if (v==p) continue;
		dfs1(v,x);
		sz[x]+=sz[v];//计算子树
		if (sz[son[x]]<sz[v]) son[x]=v;//记录x节点的重儿子
	}
}

然后进行树链剖分,按照重链将树上节点编号并映射值

这里需要注意的是,较深的节点在线段树上的编号就越大

void dfs2(int x,int t){
	id[x]=++idx;//对x节点编号并映射
	cor[idx]=a[x];//映射新值
	top[x]=t;//记录x所在重链的顶点
	if (!son[x]) return;
	dfs2(son[x],t);
	for (auto v:e[x]){
		if (v==fa[x]||v==son[x]) continue;
		dfs2(v,v);//对轻儿子新开一条重链
	}
}

按照重链的顺序映射到线段树上后,需要完成这些操作的实现

  • 1 x y z,表示将树从 \(x\)\(y\) 结点最短路径上所有节点的值都加上 \(z\)
  • 2 x y,表示求树从 \(x\)\(y\) 结点最短路径上所有节点的值之和
  • 3 x z,表示将以 \(x\) 为根节点的子树内所有节点值都加上 \(z\)
  • 4 x 表示求以 \(x\) 为根节点的子树内所有节点值之和

对路径操作:

void update_path(int x,int y,int k){
	while (top[x]!=top[y]){
		if (dep[top[x]]<dep[top[y]]) swap(x,y);
		update(id[top[x]],id[x],k,1);
		x=fa[top[x]];
	}
	if (dep[x]<dep[y]) swap(x,y);
	update(id[y],id[x],k,1);
}
int query_path(int x,int y){
	int res=0;
	while (top[x]!=top[y]){
		if (dep[top[x]]<dep[top[y]]) swap(x,y);
		res=(res+query(id[top[x]],id[x],1))%mod;
		x=fa[top[x]];
	}
	if (dep[x]>dep[y]) swap(x,y);
	res=(res+query(id[x],id[y],1))%mod;
	return res;
}

每次选择深度较深的节点向上跳,直到处于同一条重链上

如果当前的两个节点不在一条重链上,那就处理较深节点到他所在重链的顶点之间的路径

当最后两个节点处在同一条重链上时,处理这两个节点之间的路径

因为树上的节点都通过重链映射到线段树上,所以都可以通过线段树区间查询的操作处理贡献

对子树操作:

void update_tree(int x,int k){
	update(id[x],id[x]+sz[x]-1,k,1);
}
int query_tree(int x){
	return query(id[x],id[x]+sz[x]-1,1);
}

这里利用了一个性质,节点 \(x\) 的子树大小为 \(sz_x\) 由于映射的时候是按照 DFN 序列映射的,所以以节点 \(x\) 为根节点的子树在线段树上的编号也是连续映射的,那么这个子树的区间为 \([id_x,id_x+sz_x-1]\)


以洛谷P3384为例:

#include<bits/stdc++.h>
#define cintie ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define Trd int T;cin>>T;while (T--)solve();
#define LLinf 9e18
#define Iinf 2e9
#define LL long long
#define ULL unsigned long long 
#define Lc p<<1
#define Rc p<<1|1
#define lc(x) tr[x].ch[0]
#define rc(x) tr[x].ch[1]

using namespace std;

const int N=1e5+10;

struct node{
	int l,r,sum,add;
};

node tr[4*N];
int n,m,r,mod;
int a[N],cor[N],idx;
vector<int> e[N];
int fa[N],son[N],top[N];
int sz[N],dep[N],id[N];

void dfs1(int x,int p){
	dep[x]=dep[p]+1;
	sz[x]=1;
	fa[x]=p;
	for (auto v:e[x]){
		if (v==p) continue;
		dfs1(v,x);
		sz[x]+=sz[v];
		if (sz[son[x]]<sz[v]) son[x]=v;
	}
}

void dfs2(int x,int t){
	id[x]=++idx;
	cor[idx]=a[x];
	top[x]=t;
	if (!son[x]) return;
	dfs2(son[x],t);
	for (auto v:e[x]){
		if (v==fa[x]||v==son[x]) continue;
		dfs2(v,v);
	}
}

void build(int l,int r,int p){
	tr[p]={l,r,cor[l]%mod,0};
	if (l==r) return;
	int mid=l+r>>1;
	build(l,mid,Lc);
	build(mid+1,r,Rc);
	tr[p].sum=(tr[Lc].sum+tr[Rc].sum)%mod;
}

void pushdown(int p){
	if (tr[p].add){
		tr[Lc].sum=(tr[Lc].sum+tr[p].add*(tr[Lc].r-tr[Lc].l+1)%mod)%mod;
		tr[Rc].sum=(tr[Rc].sum+tr[p].add*(tr[Rc].r-tr[Rc].l+1)%mod)%mod;
		tr[Lc].add=(tr[Lc].add+tr[p].add)%mod;
		tr[Rc].add=(tr[Rc].add+tr[p].add)%mod;
		tr[p].add=0;
	}
}

void update(int x,int y,int k,int p){
	if (x<=tr[p].l&&y>=tr[p].r){
		tr[p].sum=(tr[p].sum+k*(tr[p].r-tr[p].l+1)%mod)%mod;
		tr[p].add=(tr[p].add+k)%mod;
		return;
	}
	pushdown(p);
	int mid=tr[p].l+tr[p].r>>1;
	if (x<=mid) update(x,y,k,Lc);
	if (y>mid) update(x,y,k,Rc);
	tr[p].sum=(tr[Lc].sum+tr[Rc].sum)%mod;
}

void update_path(int x,int y,int k){
	while (top[x]!=top[y]){
		if (dep[top[x]]<dep[top[y]]) swap(x,y);
		update(id[top[x]],id[x],k,1);
		x=fa[top[x]];
	}
	if (dep[x]>dep[y]) swap(x,y);
	update(id[x],id[y],k,1);
}

void update_tree(int x,int k){
	update(id[x],id[x]+sz[x]-1,k,1);
}

int query(int x,int y,int p){
	if (x<=tr[p].l&&y>=tr[p].r)
		return tr[p].sum;
	pushdown(p);
	int res=0;
	int mid=tr[p].l+tr[p].r>>1;
	if (x<=mid) res=(res+query(x,y,Lc))%mod;
	if (y>mid) res=(res+query(x,y,Rc))%mod;
	return res;
}

int query_path(int x,int y){
	int res=0;
	while (top[x]!=top[y]){
		if (dep[top[x]]<dep[top[y]]) swap(x,y);
		res=(res+query(id[top[x]],id[x],1))%mod;
		x=fa[top[x]];
	}
	if (dep[x]>dep[y]) swap(x,y);
	res=(res+query(id[x],id[y],1))%mod;
	return res;
}

int query_tree(int x){
	return query(id[x],id[x]+sz[x]-1,1);
}

void solve(){
	cin>>n>>m>>r>>mod;
	for (int i=1;i<=n;i++) cin>>a[i];
	for (int i=1;i<n;i++){
		int u,v;
		cin>>u>>v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	dfs1(r,0);
	dfs2(r,r);
	build(1,n,1);
	while (m--){
		int op,x,y,z;
		cin>>op;
		if (op==1){
			cin>>x>>y>>z;
			update_path(x,y,z);
		}
		if (op==2){
			cin>>x>>y;
			cout<<query_path(x,y)<<endl;
		}
		if (op==3){
			cin>>x>>z;
			update_tree(x,z);
		}
		if (op==4){
			cin>>x;
			cout<<query_tree(x)<<endl;
		}
	}
}

int main()
{
	cintie;
	solve();
	
	
	return 0;
}
posted @ 2025-05-04 15:34  才瓯  阅读(18)  评论(0)    收藏  举报