P3384 【模板】重链剖分/树链剖分
题目描述
如题,已知一棵包含 N N N 个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:
1 x y z
,表示将树从 x x x 到 y y y 结点最短路径上所有节点的值都加上 z z z。2 x y
,表示求树从 x x x 到 y y y 结点最短路径上所有节点的值之和。3 x z
,表示将以 x x x 为根节点的子树内所有节点值都加上 z z z。4 x
表示求以 x x x 为根节点的子树内所有节点值之和
输入格式
第一行包含 4 4 4 个正整数 N , M , R , P N,M,R,P N,M,R,P,分别表示树的结点个数、操作个数、根节点序号和取模数(即所有的输出结果均对此取模)。
接下来一行包含 N N N 个非负整数,分别依次表示各个节点上初始的数值。
接下来 N − 1 N-1 N−1 行每行包含两个整数 x , y x,y x,y,表示点 x x x 和点 y y y 之间连有一条边(保证无环且连通)。
接下来 M M M 行每行包含若干个正整数,每行表示一个操作。
输出格式
输出包含若干行,分别依次表示每个操作 2 2 2 或操作 4 4 4 所得的结果(对 P P P 取模)。
输入输出样例 #1
输入 #1
5 5 2 24
7 3 7 8 0
1 2
1 5
3 1
4 1
3 4 2
3 2 2
4 5
1 5 1 3
2 1 3
输出 #1
2
21
说明/提示
【数据规模】
对于 30 % 30\% 30% 的数据: 1 ≤ N ≤ 10 1 \leq N \leq 10 1≤N≤10, 1 ≤ M ≤ 10 1 \leq M \leq 10 1≤M≤10;
对于 70 % 70\% 70% 的数据: 1 ≤ N ≤ 10 3 1 \leq N \leq {10}^3 1≤N≤103, 1 ≤ M ≤ 10 3 1 \leq M \leq {10}^3 1≤M≤103;
对于 100 % 100\% 100% 的数据: 1 ≤ N ≤ 10 5 1\le N \leq {10}^5 1≤N≤105, 1 ≤ M ≤ 10 5 1\le M \leq {10}^5 1≤M≤105, 1 ≤ R ≤ N 1\le R\le N 1≤R≤N, 1 ≤ P ≤ 2 30 1\le P \le 2^{30} 1≤P≤230。所有输入的数均在 int
范围内。
【样例说明】
树的结构如下:
各个操作如下:
故输出应依次为 2 2 2 和 21 21 21。
解析:
1.线段树:
线段树相关文章(本文默认读者已掌握)
2.重链剖分:
①dfs序
我们将 u u u 点的dfs序定义为树上 u u u 点被深搜到的次序,记作 d f n u dfn_u dfnu
如题目图: d f n 2 = 1 , d f n 1 = 2 , d f n 3 = 3 , d f n 4 = 4 , d f n 5 = 5 dfn_2=1,dfn_1=2,dfn_3=3,dfn_4=4,dfn_5=5 dfn2=1,dfn1=2,dfn3=3,dfn4=4,dfn5=5
那么我们稍加思索可以发现任意节点其子树上的节点的 d f n dfn dfn 都大于该节点的。
因为想要遍历它的子树就必须先深搜到它,它的dfn自然小于其子树的了。
此外,我们可以发现,dfs持续进行,会有一条或多条链的 d f n dfn dfn 是连续的自然数。
因为深搜到某个点 u u u 设 d f n u = x dfn_u=x dfnu=x,那么下一步紧接着搜它某儿子 v v v,那么 d f n v = d f n u + 1 = x + 1 dfn_v=dfn_u+1=x+1 dfnv=dfnu+1=x+1,如此下来,就会有几条链的 d f n dfn dfn 是连续的
②重链
相关概念:
- 重儿子:当前节点各子树中节点数最多的那棵子树的根节点,是当前点的子节点。如果只有一个子结点就是这个节点,没有子节点就没有重儿子,有多个重儿子任取其一。
如:
1 1 1的重儿子是2,2的重儿子是4,4的重儿子是6,6的重儿子是10,3的重儿子是7,7的重儿子是8或9
有儿子节点必有重儿子,没有重儿子必定没有子节点
- 重链:每个点连接其重儿子的边所构成的链
如图:
其中由连续的红边构成的链就是重链,红边被称为重边。每一条重链的开头是第一条重边的出发点,重链起点并不会是其父重儿子。
由于总会有某些链的 d f n dfn dfn 是连续的,我们可以使这某些链就是重链。想要实现很简单,每次搜某个点的儿子时都优先搜其重儿子,那么重儿子的 d f n dfn dfn 就是该点的 + 1 1 1
并且我们发现,任何点的父亲都一定在某条重链上。
因为其父节点既然有儿子就必有重儿子,也就必有重边,重边必然是某条重链的一部分
3.解题思路:
用树剖将树上问题化成区间问题
用线段树维护区间和。区间 [ i , j ] [i,j] [i,j] 表示的是 d f n dfn dfn 分别为 i , j i,j i,j 的两节点之间的点权值和
①操作1、2
找到 x , y x,y x,y所在重链的顶端分别记为 t o p x , t o p y top_x,top_y topx,topy。
如果 t o p x = t o p y top_x=top_y topx=topy,则 x , y x,y x,y 处于同条重链, d f n dfn dfn 是连续的,直接区间 [ d f n x , d f n y ] [dfn_x,dfn_y] [dfnx,dfny] 修改/查询即可
如果 t o p x ≠ t o p y top_x≠top_y topx=topy,使 d e e p [ t o p x ] > d e e p [ t o p y ] deep[top_x]>deep[top_y] deep[topx]>deep[topy] ( d e e p [ i ] deep[i] deep[i] 为 i i i 点的深度),这可以用 s w a p ( ) swap() swap() 函数实现。
因为 d e e p [ t o p x ] > d e e p [ t o p y ] deep[top_x]>deep[top_y] deep[topx]>deep[topy],所以 x → y x→y x→y 的路径一定包含了 x → t o p [ x ] x→top[x] x→top[x] 的路径,而这条路径的 d f n dfn dfn 是连续的,区间 [ d f n x , d f n t o p x ] [dfn_x,dfn_{top_x}] [dfnx,dfntopx] 修改/查询即可。然后使 x = f a t o p x x=fa_{top_x} x=fatopx,来到其父节点,此时来到了一条新的重链上,可以重复上述过程了。最后他们一定会归于同条链,再对此时的 [ x , y ] [x,y] [x,y] 修改/查询即可
②操作3、4
记以某个点 u u u 为根的子树点数为 s i z e u size_u sizeu,那么这棵子树上的所有点的 d f n dfn dfn 都在 [ d f n u , d f n u + s i z e u − 1 ] [dfn_u,dfn_u+size_u-1] [dfnu,dfnu+sizeu−1] 中,所以修改时对这段区间修改就行了。
为了知道的 d f n dfn dfn 为 i i i 的点的编号为多少,我们还需用一个 r d f n rdfn rdfn数组 来记录。用于线段树的初始化
4.代码
#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
#define int long long
const int MAXN=800005;
int n,m,root,mod;
struct EDGE{
int to,nxt;
}edge[MAXN];
int head[MAXN];
int tot;
void add(int u,int v){
tot++;
edge[tot].nxt=head[u];
edge[tot].to=v;
head[u]=tot;
}
struct ordinary_tree{
int f,d,wc,c,size,dfn,top;
//f当前点的父亲,d当前点的深度,wc当前点的重儿子
//size当前点为根的子树大小,dfn当前点的深搜序,top当前点所在重链的顶端
}a[MAXN];//a[i]表示原编号为i的点的相关信息
void dfs1(int u,int fa){//第一次dfs求每个点的重儿子及子树大小
a[u].size=1;
a[u].f=fa;
a[u].d=a[fa].d+1;
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].to;
if(v==fa) continue;
dfs1(v,u);
a[u].size+=a[v].size;
if(a[v].size>a[a[u].wc].size) a[u].wc=v;
}
}
int rdfn[MAXN];//rdfn[x]记录dfn为x的点的编号
int vistime;
void dfs2(int u,int Top){
a[u].dfn=++vistime;//计数器得每个点的dfn
rdfn[vistime]=u;
a[u].top=Top;
if(a[u].wc!=0){//没有重儿子的话就没有儿子,直接返回
dfs2(a[u].wc,Top);//先搜重儿子,其链顶是从之前延续下来的
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].to;
if(v!=a[u].f&&v!=a[u].wc){//搜其他儿子,重儿子不要重复搜
dfs2(v,v);//其它儿子就只能自起一条新重链
}
}
}
}
/*------线段树相关内容--------*/
struct line_tree{
int l,r,w,lzy;
}t[MAXN];
void pushup(int u){
t[u].w=(t[u*2].w+t[u*2+1].w)%mod;
}
void build(int u,int L,int R){
t[u].l=L;t[u].r=R;
if(L==R){
t[u].w=a[rdfn[L]].c;//代表区间为[L,L]的线段树点的初值是 dfn为L的点 的值,即a[rdfn[L]].c
}
else{
int mid=(L+R)/2;
build(u*2,L,mid);build(u*2+1,mid+1,R);
pushup(u);
}
}
bool in(int L,int R,int l,int r){
return (L<=l)&&(R>=r);
}
bool nocr(int L,int R,int l,int r){
return (L>r)||(R<l);
}
void maketag(int u,int x){
int len=(t[u].r-t[u].l+1);
t[u].w+=len*x%mod;
t[u].lzy+=x;
t[u].w%=mod;
t[u].lzy%=mod;
}
void pushdown(int u){
maketag(u*2,t[u].lzy);
maketag(u*2+1,t[u].lzy);
t[u].lzy=0;
}
int query(int u,int L,int R){
if(in(L,R,t[u].l,t[u].r)){
return t[u].w;
}
else if(!nocr(L,R,t[u].l,t[u].r)){
pushdown(u);
return (query(u*2,L,R)+query(u*2+1,L,R))%mod;
}
else return 0;
}
void update(int u,int L,int R,int x){
if(in(L,R,t[u].l,t[u].r)){
maketag(u,x);
}
else if(!nocr(L,R,t[u].l,t[u].r)){
pushdown(u);
update(u*2,L,R,x);
update(u*2+1,L,R,x);
pushup(u);
}
}
void upd(int x,int y,int z){
while(a[x].top!=a[y].top){//如果两top相等,说明此时x,y在同条链上
if(a[a[x].top].d<a[a[y].top].d) swap(x,y);//保持x的top深度更大
update(1,a[a[x].top].dfn,a[x].dfn,z);
x=a[a[x].top].f;
}
update(1,min(a[x].dfn,a[y].dfn),max(a[x].dfn,a[y].dfn),z);
}
int qry(int x,int y){
int ans=0;
while(a[x].top!=a[y].top){
if(a[a[x].top].d<a[a[y].top].d) swap(x,y);
ans+=query(1,a[a[x].top].dfn,a[x].dfn);
ans%=mod;
x=a[a[x].top].f;
}
return (ans+query(1,min(a[x].dfn,a[y].dfn),max(a[x].dfn,a[y].dfn)))%mod;
}
signed main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>n>>m>>root>>mod;
for(int i=1;i<=n;i++) cin>>a[i].c;
int ui,vi;
for(int i=1;i<n;i++){
cin>>ui>>vi;
add(ui,vi);add(vi,ui);
}
dfs1(root,0);
dfs2(root,0);
build(1,1,n);
int x,y,z,work;
while(m--){
cin>>work;
if(work==1){
cin>>x>>y>>z;
upd(x,y,z);//路径修改
}
else if(work==2){
cin>>x>>y;
cout<<qry(x,y)%mod<<endl;//路径查询
}
else if(work==3){
cin>>x>>z;
update(1,a[x].dfn,a[x].dfn+a[x].size-1,z);//子树修改
}
else if(work==4){
cin>>x;
cout<<query(1,a[x].dfn,a[x].dfn+a[x].size-1)%mod<<endl;//子树查询
}
}
return 0;
}
码字不易,求给个赞吧QAQ
如有错误,欢迎在评论区指出!