Loading

浅谈树链剖分

基本概念

定义:将树中任意一条路径转化成 \(O(\log n)\) 段连续区间。

就是先把树转化成一条链,那么树中任意一条路径都可以转化成这条链中 \(O(\log n)\) 段连续区间,这样就可以把树上问题转化为区间的问题。

几个概念:

  • 重儿子:对于一个非叶子节点 \(x\),先求一下它的每个子树的节点总数,那么 \(x\) 的重儿子就是节点数最多的那个子树的根节点。如果存在子树节点总数相同,那么可以任选一个点作为重儿子。

  • 轻儿子:对于一个非叶子节点 \(x\),所有儿子中,不是重儿子的就是轻儿子。

  • 重边:重儿子与它的父节点之间的边。

  • 轻边:其它的边。

  • 重链:极大的由重边构成的路径。如果这个点是重儿子,那么这个点就在它父节点的重链中,否则这个点就在从这个点开始向下走的重链中。

下图中,红色的点表示重儿子,红色的边表示重边,用蓝笔圈出表示重链:

我们可以按照这个树的 DFS 序将这个树变成序列。所谓 DFS 序,就是在 DFS 中,按顺序遍历到每个点的次序,顺序:优先遍历重儿子。性质:一条重链的编号是连续的,某一棵子树的编号也是连续的。

定理:数中任意一条路径均可拆分成 \(O(\log n)\) 条重链,即可拆分成 \(O(\log n)\) 个连续区间。

拆分方式:如果要拆分某两个点的路径,先看看它们所在的重链谁更矮,就跳到重链终点的父节点,再激活另一条重链,最后一定会跳到同一条重链上。

大概像这样:

例题

题目

树链剖分后,直接用线段树。

参考代码:

#include<bits/stdc++.h>
#define mems(a,b) memset(a,b,sizeof a)
#define LL long long
using namespace std;
const int N=100010,M=200010;
int n,m,R,p;
int w[N],h[N],e[M],ne[M],idx;
int id[N],nw[N],cnt;//dfs序(新编号),新编号的权值
int dep[N],sz[N],top[N],fa[N],son[N];//深度,子树大小,重链顶点,父节点,重儿子
struct Tree{
	int l,r;
	LL add,sum;
}tr[N*4];
void add(int a,int b){
	e[idx]=b;
	ne[idx]=h[a];
	h[a]=idx++;
}
void dfs1(int u,int father,int depth){
	dep[u]=depth;
	fa[u]=father;
	sz[u]=1;
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(j==father) continue;
		dfs1(j,u,depth+1);
		sz[u]+=sz[j];
		if(sz[son[u]]<sz[j]) son[u]=j;
	}
}
void dfs2(int u,int t){//t表示当前点所在重链的顶点
	id[u]=++cnt;
	nw[cnt]=w[u];
	top[u]=t;
	if(!son[u]) return;//当前点是叶子节点
	dfs2(son[u],t);//重儿子一定和自己在一条重链内
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(j==fa[u] || j==son[u]) continue;
		dfs2(j,j);
	}
}
void pushup(int u){
	tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
}
void pushdown(int u){
	Tree &root=tr[u],&left=tr[u<<1],&right=tr[u<<1|1];
	if(root.add){
		left.add+=root.add;
		left.sum+=root.add*(left.r-left.l+1);
		right.add+=root.add;
		right.sum+=root.add*(right.r-right.l+1);
		root.add=0;
	}
}
void build(int u,int l,int r){
	tr[u]={l,r,0,nw[r]};
	if(l==r) return;
	int mid=l+r>>1;
	build(u<<1,l,mid);
	build(u<<1|1,mid+1,r);
	pushup(u);
}
void update(int u,int l,int r,int k){
	if(l<=tr[u].l && r>=tr[u].r){
		tr[u].add+=k;
		tr[u].sum+=k*(tr[u].r-tr[u].l+1);
		return;
	}
	pushdown(u);
	int mid=tr[u].l+tr[u].r>>1;
	if(l<=mid) update(u<<1,l,r,k);
	if(r>mid) update(u<<1|1,l,r,k);
	pushup(u);
}
LL query(int u,int l,int r){
	if(l<=tr[u].l && r>=tr[u].r) return tr[u].sum;
	pushdown(u);
	int mid=tr[u].l+tr[u].r>>1;
	LL res=0;
	if(l<=mid) res+=query(u<<1,l,r)%p;
	if(r>mid) res+=query(u<<1|1,l,r)%p;
	return res%p;
}
void update_path(int u,int v,int k){
	while(top[u]!=top[v]){//不在同一条重链中
		if(dep[top[u]]<dep[top[v]]) swap(u,v);//使top[v]在top[u]上面
		update(1,id[top[u]],id[u],k);
		u=fa[top[u]];
	}
	if(dep[u]<dep[v]) swap(u,v);//使v在u上面
	update(1,id[v],id[u],k);
}
LL query_path(int u,int v){
	LL res=0;
	while(top[u]!=top[v]){//不在同一条重链中
		if(dep[top[u]]<dep[top[v]]) swap(u,v);//使top[v]在top[u]上面
		res+=query(1,id[top[u]],id[u])%p;
		u=fa[top[u]];
	}
	if(dep[u]<dep[v]) swap(u,v);//使v在u上面
	res+=query(1,id[v],id[u])%p;
	return res%p;
}
void update_tree(int u,int k){
	update(1,id[u],id[u]+sz[u]-1,k);
}
LL query_tree(int u){
	return query(1,id[u],id[u]+sz[u]-1)%p;
}
int main(){
	cin>>n>>m>>R>>p;
	for(int i=1;i<=n;i++) scanf("%d",&w[i]);
	mems(h,-1);
	for(int i=1;i<n;i++){
		int a,b;
		scanf("%d%d",&a,&b);
		add(a,b);
		add(b,a);
	}
	dfs1(R,-1,1);//求每个点的重儿子
	dfs2(R,R);//求DFS序,根节点重链起点为自身
	build(1,1,n);//建立线段树
	while(m--){
		int t,u,v,k;
		scanf("%d%d",&t,&u);
		if(t==1){
			scanf("%d%d",&v,&k);
			update_path(u,v,k);
		}
		else if(t==2){
			scanf("%d",&v);
			printf("%lld\n",query_path(u,v));
		}
		else if(t==3){
			scanf("%d",&k);
			update_tree(u,k);
		}
		else printf("%lld\n",query_tree(u));
	}
	return 0;
}

题目

为了方便,下标从 \(1\) 开始。

如果要安装一个点,就要先安装从根节点到这个点路径上的所有点;如果要删除一个点,就要先删除以这个点为根的子树。

每个点只有两种状态:

  • 已安装,这里用 \(1\) 表示。

  • 未安装,这里用 \(0\) 表示。

两种操作(设操作前和为 \(a\),操作后和为 \(b\)):

  • 安装 \(x\):将根到 \(x\) 的路径上的所有点变成 \(1\),操作总数:\(b-a\)

  • 卸载 \(x\):将以 \(x\) 为根的子树上的所有点变成 \(0\),操作总数:\(a-b\)

转化到区间操作:

  • 将某段全变成 \(t\)

  • 查询区间和。

懒标记 flag 有三种取值:

  • \(-1\),表示无操作。

  • \(1\),表示全变成 \(1\)

  • \(0\),表示全变成 \(0\)

参考代码:

#include<bits/stdc++.h>
#define mems(a,b) memset(a,b,sizeof a)
using namespace std;
const int N=100010;
int n,m;
int h[N],e[N],ne[N],idx;
int id[N],cnt;
int dep[N],sz[N],top[N],fa[N],son[N];
struct Tree{
	int l,r;
	int flag,sum;
}tr[N*4];
void add(int a,int b){
	e[idx]=b;
	ne[idx]=h[a];
	h[a]=idx++;
}
void dfs1(int u,int depth){
	dep[u]=depth;
	sz[u]=1;
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		dfs1(j,depth+1);
		sz[u]+=sz[j];
		if(sz[son[u]]<sz[j]) son[u]=j;
	}
}
void dfs2(int u,int t){
	id[u]=++cnt;
	top[u]=t;
	if(!son[u]) return;
	dfs2(son[u],t);
	for(int i=h[u];~i;i=ne[i]){
		int j=e[i];
		if(j==son[u]) continue;
		dfs2(j,j);
	}
}
void pushup(int u){
	tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
}
void pushdown(int u){
	Tree &root=tr[u],&left=tr[u<<1],&right=tr[u<<1|1];
	if(root.flag!=-1){
		left.sum=root.flag*(left.r-left.l+1);
		right.sum=root.flag*(right.r-right.l+1);
		left.flag=right.flag=root.flag;
		root.flag=-1;
	}
}
void build(int u,int l,int r){
	tr[u]={l,r,-1,0};
	if(l==r) return;
	int mid=l+r>>1;
	build(u<<1,l,mid);
	build(u<<1|1,mid+1,r);
}
void update(int u,int l,int r,int k){
	if(l<=tr[u].l && r>=tr[u].r){
		tr[u].flag=k;
		tr[u].sum=k*(tr[u].r-tr[u].l+1);
		return;
	}
	int mid=tr[u].l+tr[u].r>>1;
	pushdown(u);
	if(l<=mid) update(u<<1,l,r,k);
	if(r>mid) update(u<<1|1,l,r,k);
	pushup(u);
}
void update_path(int u,int v,int k){
	while(top[u]!=top[v]){
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		update(1,id[top[u]],id[u],k);
		u=fa[top[u]];
	}
	if(dep[u]<dep[v]) swap(u,v);
	update(1,id[v],id[u],k);
}
void update_tree(int u,int k){
	update(1,id[u],id[u]+sz[u]-1,k);
}
int main(){
	cin>>n;
	mems(h,-1);
	for(int i=2;i<=n;i++){
		int p;
		scanf("%d",&p);
		p++;
		add(p,i);
		fa[i]=p;
	}
	dfs1(1,1);
	dfs2(1,1);
	build(1,1,n);
	cin>>m;
	char op[20];
	int u;
	while(m--){
		scanf("%s%d",op,&u);
		u++;
		if(!strcmp(op,"install")){
			int t=tr[1].sum;
			update_path(1,u,1);
			printf("%d\n",tr[1].sum-t);
		}
		else{
			int t=tr[1].sum;
			update_tree(u,0);
			printf("%d\n",t-tr[1].sum);
		}
	}
	return 0;
}
posted @ 2025-07-25 20:54  liushuangning  阅读(28)  评论(0)    收藏  举报