树链剖分

树链剖分#

前言#

我认为树链剖分是一种工具而不是数据结构

它能让你处理树上的链的操作

感觉像是 序列 树 的一种媒介,序列问题 + 树剖 = 树上问题

是这样没错了

模板P3384#

题意

给你一颗树,需要支持以下操作:

  • 1 x y z,表示将树从 xy 结点最短路径上所有节点的值都加上 z

  • 2 x y,表示求树从 xy 结点最短路径上所有节点的值之和

  • 3 x z,表示将以 x 为根节点的子树内所有节点值都加上 z

  • 4 x 表示求以 x 为根节点的子树内所有节点值之和

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=4e5+5;
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<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*f;
}
#define lsp p<<1
#define rsp p<<1|1
int n,m,r,mod;
int tot=1,ver[N],edge[N],nxt[N],head[N];
int w[N],wt[N];
int t[N<<2],lz[N<<2];
int son[N],id[N],fa[N],cnt,dep[N],siz[N],top[N];
int res=0;
inline void add(int x,int y,int z){
	ver[++tot]=y,edge[tot]=z,nxt[tot]=head[x],head[x]=tot;
}

//segement tree
inline void push_up(int p){
	t[p]=(t[lsp]+t[rsp])%mod;
}
inline void push_down(int p,int len){
	lz[lsp]+=lz[p],lz[rsp]+=lz[p];
	t[lsp]+=lz[p]*(len-(len>>1));
	t[rsp]+=lz[p]*(len>>1);
	t[lsp]%=mod,t[rsp]%=mod;
	lz[p]=0;
}
inline void build(int p,int l,int r){
	if(l==r){
		t[p]=wt[l]%mod;
		return;
	}
	int mid=l+r>>1;
	build(lsp,l,mid);
	build(rsp,mid+1,r);
	push_up(p);
}
inline int query(int p,int l,int r,int L,int R){
	int res=0;
	if(L<=l&&r<=R){
		res=(res+t[p])%mod;
		return res;
	}
	if(lz[p])
		push_down(p,r-l+1);
	int mid=l+r>>1;
	if(L<=mid) res=(res+query(lsp,l,mid,L,R))%mod;
	if(R>mid) res=(res+query(rsp,mid+1,r,L,R))%mod;
	return res%mod;
}
inline void update(int p,int l,int r,int L,int R,int k){
	if(L<=l&&r<=R){
		lz[p]=(lz[p]+k)%mod;
		t[p]=(t[p]+(r-l+1)*k)%mod;
		return;
	}
	if(lz[p]) push_down(p,r-l+1);
	int mid=l+r>>1;
	if(L<=mid) update(lsp,l,mid,L,R,k);
	if(R>mid) update(rsp,mid+1,r,L,R,k);
	push_up(p);
}

//tree dfs
inline void dfs1(int x,int f){
	fa[x]=f,dep[x]=dep[f]+1,siz[x]=1;
	int maxson=-1;
	for(int i=head[x];i;i=nxt[i]){
		int to=ver[i];
		if(to==f) continue;
		dfs1(to,x);
		siz[x]+=siz[to];
		if(siz[to]>maxson) son[x]=to,maxson=siz[to];
	}
}
inline void dfs2(int x,int topf){
	id[x]=++cnt,wt[cnt]=w[x];
	top[x]=topf;
	if(!son[x]) return;
	dfs2(son[x],topf);
	for(int i=head[x];i;i=nxt[i]){
		int to=ver[i];
		if(to==son[x]||to==fa[x]) continue;
		dfs2(to,to);
	}
}
inline void query_upd(int x,int y,int k){
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		update(1,1,n,id[top[x]],id[x],k);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	update(1,1,n,id[x],id[y],k);
}
inline int query_sum(int x,int y){
	int ans=0;
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		ans=(ans+query(1,1,n,id[top[x]],id[x]))%mod;
		x=fa[top[x]];
	}
	if(dep[x]>dep[y])
		swap(x,y);
	return (ans+query(1,1,n,id[x],id[y]))%mod;
}
inline int query_son(int x){
	return query(1,1,n,id[x],id[x]+siz[x]-1);
}
inline void upd_son(int x,int k){
	update(1,1,n,id[x],id[x]+siz[x]-1,k);
}

signed main(){
	n=read(),m=read(),r=read(),mod=read();
	for(int i=1;i<=n;++i)
		w[i]=read();
	for(int i=1,u,v;i<n;++i){
		u=read(),v=read();
		add(u,v,1),add(v,u,1);
	}
	dep[0]=1;
	dfs1(r,0);
	dfs2(r,r);
	build(1,1,n);
	while(m--){
		int op=read(),x,y,z;
		if(op==1){
			x=read(),y=read(),z=read();
			query_upd(x,y,z%mod);
		}
		if(op==2){
			x=read(),y=read();
			printf("%lld\n",query_sum(x,y)%mod);
		}
		if(op==3){
			x=read(),y=read();
			upd_son(x,y%mod);
		}
		if(op==4){
			x=read();
			printf("%lld\n",query_son(x)%mod);
		}
	}
}

P2590#

题意

给你一颗树,需要支持以下操作:

  • CHANGE u t : 把结点 u 的权值改为 t

  • QMAX u v: 询问从点 u 到点 v 的路径上的节点的最大权值

  • QSUM u v: 询问从点 u 到点 v 的路径上的节点的权值和

思路

树链剖分 + 单点修改区间查询线段树

code

P3178#

题意

给你一颗树,需要支持以下操作:

  • 操作 1 :把某个节点 x 的点权增加 a
  • 操作 2 :把某个节点 x 为根的子树中所有点的点权都增加 a
  • 操作 3 :询问某个节点 x 到根的路径中所有点的点权和

思路

树链剖分 + 区间修改区间查询线段树

code

P3833#

题意

给你一颗树,需要支持以下操作:

  • A u v d :将点 uv 之间的路径上的所有点的点权都加上 d
  • Q u :询问以 x 为根的子树权值和

思路

树链剖分 + 区间修改区间查询线段树

code

P2146#

题意

给你一颗树,需要支持以下操作:

  • install x :将 1x 之间的路径上所有点的点权推平为 1 ,询问有多少点权改变了
  • uninstall x :将 x 的子树中所有点点权推平为 0,询问有多少点权改变了

思路

树链剖分 + 区间推平区间查询线段树

code

P4114#

题意

给你一颗树,需要支持以下操作:

  • CHANGE i t :把第 i 条边的边权变成 t
  • QUERY a b :输出从 ab 的路径上最大的边权,当 a=b 时,输出 0

思路

边权树剖

考虑把父亲与儿子之间的边权变成儿子的点权

QUERY 操作时查询点权的 max 就行

注意:不能算 LCA(a,b) 的点权

code

P4315#

题意

给你一颗树,需要支持以下操作:

  • Change k w:将第 k 条边上权值改为 w

  • Cover u v w:将节点 u 与节点 v 之间的边权都改为 w

  • Add u v w:将节点 u 与节点 v 之间的边权都增加 w

  • Max u v:询问节点 u 与节点 v 之间边权最大值

思路

边权转点权树剖

有两种区间修改操作,lz1 是推平的懒标记,lz2 是区间加的懒标记

细节:优先进行推平,推平时把更新的左右儿子的 lz2 改为 0

code

P1505#

题意

给你一颗树,需要支持以下操作:

  • C i w 将输入的第 i 条边权值改为 w
  • N u vu,v 节点之间的边权都变为相反数
  • SUM u v 询问 u,v 节点之间边权和
  • MAX u v 询问 u,v 节点之间边权最大值
  • MIN u v 询问 u,v 节点之间边权最小值

思路

树链剖分

code

CF343D#

题意

给你一颗树,需要支持以下操作:

    1. 将点 u 和其子树上的所有节点的权值改为 1
    1. 将点 u1 的路径上的所有节点的权值改为 0
    1. 询问点 u 的权值

思路

树链剖分

code

CF877E#

题意

给你一颗树,需要支持以下操作:

  • get : 询问一个点 x 的子树里有多少个 1

  • pow : 将一个点 x 的子树中所有节点取反

思路

树链剖分

code

P6157#

题意

给你一颗树,第 i 个点的点权为 wi

每一次给出一条链,小 A 可以从这条链上找出两个点权不同的点 x,y,他的得分是 wxmodwy

然后小 B 会从整棵树中选取两个小 A 没有选过的点,计分方式同小 A

求小 A 得分最大值,与在此情况下小 B 的得分最大值

有时会有增加一个点的权值的操作

思路

A 的最大值一定是链内严格第二大,用树剖维护

B 的得分可用 multiset 维护

code

P3979#

题意

给你一颗树,需要支持以下操作:

  • opt=1,把首都修改为 id

  • opt=2,将 x y 路径上的所有城市的防御值修改为 v

  • opt=3,询问以城市 id 为根的子树中的最小防御值

思路

先以 1 为根树剖

操作 1,2 十分容易

操作 3 分情况讨论:(记根为 now,查询的城市为 x)

  • x=rt,返回 t[1].mn
  • now 不在 1 为根时 x 的子树内,这时候没有影响,直接统计
  • xnow 祖先

对于 3 ,我们找到 xnow 链上 x 的儿子 y

发现以 now 为根的时候 y 的子树计算不到,扣掉就行

code

P2486#

题意

给定一棵 n 个节点的无根树,共有 m 个操作,操作分为两种:

  1. 将节点 a 到节点 b 的路径上的所有点(包括 ab)都染成颜色 c
  2. 询问节点 a 到节点 b 的路径上的颜色段数量。

颜色段的定义是极长的连续相同颜色被认为是一段。例如 112221 由三段组成:112221

思路

前面的题都是照着板子打的 (因为板子不变),这道题能够帮人更好的理解树剖的本质

线段树需要维护 lc (左端点颜色) rc (右端点颜色) sum (颜色段数量)

合并左右两子区间的时候,t[p].sum=t[lsp].sum+t[rsp].sum(t[lsp].rc=t[rsp].lc)

对于路径求和的部分,我们不能简单的把 sum 累加

在左右两点不断向上跳的过程中,左边跳的区间是连续的,右边也是

因此需要在加之后判断两边接上的区间左右端点颜色是否相同,若相同答案

这个需要在跑线段树的时候记录一下 LcRc

注意:跳到最顶上了也要判断一下

code

作者:Into_qwq

出处:https://www.cnblogs.com/into-qwq/p/16437080.html

版权:本作品采用「qwq」许可协议进行许可。

posted @   Into_qwq  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示