重链剖分/树链剖分
题目来源:https://www.luogu.com.cn/problem/P3384
//
题意:在一颗树上,修改(查询)两点(最短路径)或一点子树上的所有权值。
//
算法:进行剖分:
教程(3个):
https://www.cnblogs.com/chinhhh/p/7965433.html
【《树链剖分:重链剖分》】https://www.bilibili.com/video/BV1qv421C7BE?vd_source=edd8d483423d58308aefa72fbec9bd22
【D11 树链剖分 P3379【模板】最近公共祖先(LCA)】https://www.bilibili.com/video/BV1tY4y1G7em?vd_source=edd8d483423d58308aefa72fbec9bd22
剖分后所有重链上的新节点编号都是连续的,一个节点子树的编号都是连续的。
1:两个dfs的作用
2:build建树
3:在两节点之间的查询(修改),需要进行跳点,x始终保持为所在链顶端的深度更深的那个点,知道两个点处于同一条链上,然后区间查询query(dfn[x],dfn[y])。
注意qRange函数:两点之间的查询,是选择深度更深的那个点,然后区间查询[ dfn[top[x]],dfn[x] ],知道最后在一个重链上,然后还是让x保持 深度小的点,区间查询 [ dfn[x],dfn[y] ],我个人认为一直让x保持深度更深的位置,因为区间查询更方便,[x,y]区间两点一定是x<=y的,跳点也方便。
//
题解:题解注释也很清楚
点击查看代码
//树链剖分
#include<bits/stdc++.h>
#define int long long
#define lp p<<1
#define rp p<<1 |1
using namespace std;
const int N=1e5+9;
vector<int>w(N),fa(N),siz(N),son(N),dep(N),top(N),dfn(N),num(N),tree(N<<2),lazy(N<<2);
vector<int>G[N];
int tot;
int n,q,R,Mod;//r根节点
/* fa[]:两点查询的跳点
* size[u]:u的子树编号都是连续的,用于查询子树的权值和修改
* son[u]:u节点的重儿子编号,用于dfs2往重链方向递归,得到top[]
* dep[u]:u节点的深度:用于挑点过程中保证一直是深度更深的节点 在跳点
* */
void dfs1(int u,int f){//fa[],size[],son[],dep[]
fa[u]=f,siz[u]=1,dep[u]=dep[f]+1;
for(auto v:G[u]){
if(v==f) { continue;}
dfs1(v,u);
siz[u]+=siz[v];//统计u的子节点个数
if(siz[son[u]]<siz[v]){son[u]=v;}//如果u当前的重儿子子树数 小于 v儿子节点的子树总数,u的重儿子更新
}
}
/* top[u]:u节点所在重链中的顶点,1:用于判断两点是否在同一重链上; 2:跳点
* dfn[u]:重链剖分各节点的新编号
* num[u]:新编号映射
* */
void dfs2(int u,int f){
dfn[u]=++tot,num[tot]=u,top[u]=f;
if(!son[u]) return;//u节点没有重儿子了
dfs2(son[u],f);
for(auto v:G[u]){//访问轻儿子
if(v==fa[u] || v==son[u]) {continue;}
dfs2(v,v);//轻儿子的重链顶点就是自己
}
}
void pushup(int p){
tree[p]=(tree[lp]+tree[rp])%Mod;
}
void pushdown(int p,int len){
if(lazy[p]!=0){
lazy[lp]+=lazy[p]%Mod,lazy[rp]+=lazy[p]%Mod;
tree[lp]+=(len-(len>>1))*lazy[p]%Mod,tree[rp]+=(len>>1)*lazy[p]%Mod;
lazy[p]=0;
}
}
void build(int s,int t,int p){
if(s==t){
tree[p]=w[num[s]]%Mod;//权值还是存在原节点,要映射回去
return;
}
int m=(s+t)>>1;
build(s,m,lp),build(m+1,t,rp);
pushup(p);
}
int query(int l,int r,int s,int t,int p){
if(l<=s && r>=t){
return tree[p]%Mod;
}
pushdown(p,t-s+1);
int m=(s+t)>>1,sum=0;
if(l<=m) {sum=query(l,r,s,m,lp)%Mod;}
if(r>=m+1) {sum+=query(l,r,m+1,t,rp)%Mod;}
pushup(p);
return sum%Mod;
}
void update(int l,int r,int k,int s,int t,int p){
if(l<=s && r>=t){
lazy[p]+=k%Mod;
tree[p]+=(t-s+1)*k%Mod;
return;
}
pushdown(p,t-s+1);
int m=(s+t)>>1;
if(l<=m) {update(l,r,k,s,m,lp);}
if(r>=m+1) {update(l,r,k,m+1,t,rp);}
pushup(p);
}
int qRange(int x,int y){//跳点查询
int sum=0;
while(top[x]!=top[y]){//不在同重链上,一直跳
if(dep[top[x]]<dep[top[y]]) {swap(x,y);}//x点保持为 所在链顶端的深度更深的那个点
sum+=query(dfn[top[x]],dfn[x],1,n,1)%Mod;//sum加上x点到x所在链顶端 这一段区间的点权和
x=fa[top[x]];//跳点:x跳到x所在链顶端的那个点的上面一个点
}
//直到两个点处于一条链上
if(dep[x]>dep[y]) {swap(x,y);}
sum+=query(dfn[x],dfn[y],1,n,1)%Mod;
return sum%Mod;
}
void upRange(int x,int y,int k){
k%=Mod;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) {swap(x,y);}//比较的是 链顶端更深,比较谁深,就要跳到top上比较dep
update(dfn[top[x]],dfn[x],k,1,n,1);
x=fa[top[x]];//跳点
}
if(dep[x]>dep[y]) {swap(x,y);}
update(dfn[x],dfn[y],k,1,n,1);
}
signed main()
{
cin>>n>>q>>R>>Mod;
for(int i=1;i<=n;i++){cin>>w[i];}//节点权值
int u,v;
for(int i=1;i<=n-1;i++){
cin>>u>>v;
G[u].push_back(v);
G[v].push_back(u);
}
dfs1(R,R);
dfs2(R,R);
build(1,n,1);
int op,x,y,z;
while(q--){
cin>>op;
if(op==1){//x到y的dfs路 权值+z
cin>>x>>y>>z;
upRange(x,y,z);
}
else if(op==2){//查询x到y的dfs路 权值和
cin>>x>>y;
cout<<qRange(x,y)<<'\n';
}
else if(op==3){//x子树都加上权值z
cin>>x>>z;
update(dfn[x],dfn[x]+siz[x]-1,z,1,n,1);
}
else if(op==4){//查询x子树的权值
cin>>x;
cout<<query(dfn[x],dfn[x]+siz[x]-1,1,n,1)<<'\n';//dfn[x]+siz[x]-1:u子树最后连续编号
}
}
return 0;
}
浙公网安备 33010602011771号