树链剖分
作者:互联网
树链剖分
前言
我认为树链剖分是一种工具而不是数据结构
它能让你处理树上的链的操作
感觉像是 序列 \(\rightarrow\) 树 的一种媒介,序列问题 \(+\) 树剖 \(=\) 树上问题
是这样没错了
模板P3384
题意:
给你一颗树,需要支持以下操作:
-
1 x y z
,表示将树从 \(x\) 到 \(y\) 结点最短路径上所有节点的值都加上 \(z\) -
2 x y
,表示求树从 \(x\) 到 \(y\) 结点最短路径上所有节点的值之和 -
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\) 的路径上的节点的权值和
思路:
树链剖分 \(+\) 单点修改区间查询线段树
P3178
题意:
给你一颗树,需要支持以下操作:
- 操作 1 :把某个节点 x 的点权增加 a
- 操作 2 :把某个节点 x 为根的子树中所有点的点权都增加 a
- 操作 3 :询问某个节点 x 到根的路径中所有点的点权和
思路:
树链剖分 \(+\) 区间修改区间查询线段树
P3833
题意:
给你一颗树,需要支持以下操作:
A u v d
:将点 \(u\) 和 \(v\) 之间的路径上的所有点的点权都加上 \(d\)Q u
:询问以 \(x\) 为根的子树权值和
思路:
树链剖分 \(+\) 区间修改区间查询线段树
P2146
题意:
给你一颗树,需要支持以下操作:
install x
:将 \(1\) 和 \(x\) 之间的路径上所有点的点权推平为 \(1\) ,询问有多少点权改变了uninstall x
:将 \(x\) 的子树中所有点点权推平为 \(0\),询问有多少点权改变了
思路:
树链剖分 \(+\) 区间推平区间查询线段树
P4114
题意:
给你一颗树,需要支持以下操作:
CHANGE i t
:把第 \(i\) 条边的边权变成 \(t\)QUERY a b
:输出从 \(a\) 到 \(b\) 的路径上最大的边权,当 \(a=b\) 时,输出 \(0\)
思路:
边权树剖
考虑把父亲与儿子之间的边权变成儿子的点权
在 QUERY
操作时查询点权的 \(max\) 就行
注意:不能算 \(LCA(a,b)\) 的点权
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\)
P1505
题意:
给你一颗树,需要支持以下操作:
C i w
将输入的第 \(i\) 条边权值改为 \(w\)N u v
将 \(u,v\) 节点之间的边权都变为相反数SUM u v
询问 \(u,v\) 节点之间边权和MAX u v
询问 \(u,v\) 节点之间边权最大值MIN u v
询问 \(u,v\) 节点之间边权最小值
思路:
树链剖分
CF343D
题意:
给你一颗树,需要支持以下操作:
-
- 将点 \(u\) 和其子树上的所有节点的权值改为 \(1\)
-
- 将点 \(u\) 到 \(1\) 的路径上的所有节点的权值改为 \(0\)
-
- 询问点 \(u\) 的权值
思路:
树链剖分
CF877E
题意:
给你一颗树,需要支持以下操作:
-
get
: 询问一个点 \(x\) 的子树里有多少个 \(1\) -
pow
: 将一个点 \(x\) 的子树中所有节点取反
思路:
树链剖分
P6157
题意:
给你一颗树,第 \(i\) 个点的点权为 \(w_i\)。
每一次给出一条链,小 A 可以从这条链上找出两个点权不同的点 \(x,y\),他的得分是 \(w_x\bmod w_y\)
然后小 B 会从整棵树中选取两个小 A 没有选过的点,计分方式同小 A
求小 A 得分最大值,与在此情况下小 B 的得分最大值
有时会有增加一个点的权值的操作
思路:
小 \(A\) 的最大值一定是链内严格第二大,用树剖维护
小 \(B\) 的得分可用 \(multiset\) 维护
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\) 的子树内,这时候没有影响,直接统计
- \(x\) 为 \(now\) 祖先
对于 \(3\) ,我们找到 \(x\rightarrow now\) 链上 \(x\) 的儿子 \(y\)
发现以 \(now\) 为根的时候 \(y\) 的子树计算不到,扣掉就行
P2486
题意:
给定一棵 \(n\) 个节点的无根树,共有 \(m\) 个操作,操作分为两种:
- 将节点 \(a\) 到节点 \(b\) 的路径上的所有点(包括 \(a\) 和 \(b\))都染成颜色 \(c\)。
- 询问节点 \(a\) 到节点 \(b\) 的路径上的颜色段数量。
颜色段的定义是极长的连续相同颜色被认为是一段。例如 112221
由三段组成:11
、222
、1
。
思路:
前面的题都是照着板子打的 (因为板子不变),这道题能够帮人更好的理解树剖的本质
线段树需要维护 \(lc\) (左端点颜色) \(rc\) (右端点颜色) \(sum\) (颜色段数量)
合并左右两子区间的时候,\(t[p].sum=t[lsp].sum+t[rsp].sum-(t[lsp].rc=t[rsp].lc)\)
对于路径求和的部分,我们不能简单的把 \(sum\) 累加
在左右两点不断向上跳的过程中,左边跳的区间是连续的,右边也是
因此需要在加之后判断两边接上的区间左右端点颜色是否相同,若相同答案 \(--\)
这个需要在跑线段树的时候记录一下 \(Lc\) 和 \(Rc\)
注意:跳到最顶上了也要判断一下
标签:code,题意,剖分,int,边权,树链,点权,节点 来源: https://www.cnblogs.com/into-qwq/p/16437080.html