树链剖分
树链剖分#
前言#
我认为树链剖分是一种工具而不是数据结构
它能让你处理树上的链的操作
感觉像是 序列 树 的一种媒介,序列问题 树剖 树上问题
是这样没错了
模板P3384#
题意:
给你一颗树,需要支持以下操作:
-
1 x y z
,表示将树从 到 结点最短路径上所有节点的值都加上 -
2 x y
,表示求树从 到 结点最短路径上所有节点的值之和 -
3 x z
,表示将以 为根节点的子树内所有节点值都加上 -
4 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
: 把结点 的权值改为 -
QMAX u v
: 询问从点 到点 的路径上的节点的最大权值 -
QSUM u v
: 询问从点 到点 的路径上的节点的权值和
思路:
树链剖分 单点修改区间查询线段树
P3178#
题意:
给你一颗树,需要支持以下操作:
- 操作 1 :把某个节点 x 的点权增加 a
- 操作 2 :把某个节点 x 为根的子树中所有点的点权都增加 a
- 操作 3 :询问某个节点 x 到根的路径中所有点的点权和
思路:
树链剖分 区间修改区间查询线段树
P3833#
题意:
给你一颗树,需要支持以下操作:
A u v d
:将点 和 之间的路径上的所有点的点权都加上Q u
:询问以 为根的子树权值和
思路:
树链剖分 区间修改区间查询线段树
P2146#
题意:
给你一颗树,需要支持以下操作:
install x
:将 和 之间的路径上所有点的点权推平为 ,询问有多少点权改变了uninstall x
:将 的子树中所有点点权推平为 ,询问有多少点权改变了
思路:
树链剖分 区间推平区间查询线段树
P4114#
题意:
给你一颗树,需要支持以下操作:
CHANGE i t
:把第 条边的边权变成QUERY a b
:输出从 到 的路径上最大的边权,当 时,输出
思路:
边权树剖
考虑把父亲与儿子之间的边权变成儿子的点权
在 QUERY
操作时查询点权的 就行
注意:不能算 的点权
P4315#
题意:
给你一颗树,需要支持以下操作:
-
Change k w
:将第 条边上权值改为 -
Cover u v w
:将节点 与节点 之间的边权都改为 -
Add u v w
:将节点 与节点 之间的边权都增加 -
Max u v
:询问节点 与节点 之间边权最大值
思路:
边权转点权树剖
有两种区间修改操作, 是推平的懒标记, 是区间加的懒标记
细节:优先进行推平,推平时把更新的左右儿子的 改为
P1505#
题意:
给你一颗树,需要支持以下操作:
C i w
将输入的第 条边权值改为N u v
将 节点之间的边权都变为相反数SUM u v
询问 节点之间边权和MAX u v
询问 节点之间边权最大值MIN u v
询问 节点之间边权最小值
思路:
树链剖分
CF343D#
题意:
给你一颗树,需要支持以下操作:
-
- 将点 和其子树上的所有节点的权值改为
-
- 将点 到 的路径上的所有节点的权值改为
-
- 询问点 的权值
思路:
树链剖分
CF877E#
题意:
给你一颗树,需要支持以下操作:
-
get
: 询问一个点 的子树里有多少个 -
pow
: 将一个点 的子树中所有节点取反
思路:
树链剖分
P6157#
题意:
给你一颗树,第 个点的点权为 。
每一次给出一条链,小 A 可以从这条链上找出两个点权不同的点 ,他的得分是
然后小 B 会从整棵树中选取两个小 A 没有选过的点,计分方式同小 A
求小 A 得分最大值,与在此情况下小 B 的得分最大值
有时会有增加一个点的权值的操作
思路:
小 的最大值一定是链内严格第二大,用树剖维护
小 的得分可用 维护
P3979#
题意:
给你一颗树,需要支持以下操作:
-
,把首都修改为
-
,将 路径上的所有城市的防御值修改为
-
,询问以城市 为根的子树中的最小防御值
思路:
先以 为根树剖
操作 十分容易
操作 分情况讨论:(记根为 ,查询的城市为 )
- ,返回
- 不在 为根时 的子树内,这时候没有影响,直接统计
- 为 祖先
对于 ,我们找到 链上 的儿子
发现以 为根的时候 的子树计算不到,扣掉就行
P2486#
题意:
给定一棵 个节点的无根树,共有 个操作,操作分为两种:
- 将节点 到节点 的路径上的所有点(包括 和 )都染成颜色 。
- 询问节点 到节点 的路径上的颜色段数量。
颜色段的定义是极长的连续相同颜色被认为是一段。例如 112221
由三段组成:11
、222
、1
。
思路:
前面的题都是照着板子打的 (因为板子不变),这道题能够帮人更好的理解树剖的本质
线段树需要维护 (左端点颜色) (右端点颜色) (颜色段数量)
合并左右两子区间的时候,
对于路径求和的部分,我们不能简单的把 累加
在左右两点不断向上跳的过程中,左边跳的区间是连续的,右边也是
因此需要在加之后判断两边接上的区间左右端点颜色是否相同,若相同答案
这个需要在跑线段树的时候记录一下 和
注意:跳到最顶上了也要判断一下
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现