直接贴模板 带注释
- DFS序:一颗子树在DFS序上是连续的一段 。
那么我们在进行一些操作像什么:将以某节点为根的子树节点都加上x时,就可以转化为在线段树内的区间加了。
树链剖分的一些概念:
- 重儿子(节点):子树结点数目最多的孩子结点
- 轻儿子(节点):除了重儿子以外的孩子结点
- 重边:重儿子和父节点连的边
- 轻边:轻儿子和父节点连的边
- 重链:重边连成的链
- 轻链:轻边连成的链
如图,被圈起来的就是重儿子,红色的边就是重边。
在DFS过程中,可以得到每个节点深度,父节点,重儿子,子树的节点个数。
- 从根到某一点的路径上,不超过logn条轻边和不超过logn条重边。
得到了重儿子,轻儿子,我们需要把重边轻边串成链。即:让重儿子在区间上是连续的。
这里我们就要用到DFS序了:从根开始dfs,打上dfs序,记录链顶元素,优先走重儿子。走轻儿子的时候把轻儿子设为其链顶元素。
void dfs2(int rt,int t)//t是rt所在链的链顶
{
pos[++cnt]=rt;//dfs序数组
in[rt]=cnt;
top[rt]=t;//链顶
if(son[rt])//重儿子存在
{
dfs2(son[rt],t);
}
for(int i=head[rt];i;i=e[i].next)
{
int to=e[i].to;
if(to == fa[rt] || to == son[rt]) continue;
dfs2(to,to);
}
}
这样得到了每条链在dfs序上都是连续的,如果我们需要修改树上两个顶点最短路径上的点,那么就可以转化成区间修改了。
比如我们要修改x y最短路径上的点权:
- 如果x y位于同一条链(设in[x]<in[y]),那么直接区间修改
[in[x],in[y]]
- 否则,设x为链顶更深的那个节点,fx为其链顶,先区间修改
[in[fx],in[x]]
,再把x更新为x=fa[fx]
,然后fx=top[x]
- 循环第二步,直到x y链顶相同,执行第一步
inline void updata_chain(int x,int y,int z)//x y最短路径上的点 点权+z
{
int fx=top[x],fy=top[y];
while(fx != fy)
{
if(dep[fx] < dep[fy]) swap(x,y),swap(fx,fy);//保证fx是链顶最深的点的链顶
//此时得到一条连续的链 fx->x 修改这个区间
updata(1,1,n,in[fx],in[x],z);
x=fa[fx];//fx->x这条链改完了 x变成链顶的父节点 继续修改
fx=top[x];
}
//此时x y在同一条链当中 (深度小的那个点就是原先x、y的LCA 且x,y之间的最短距离就是d[x]+d[y]-2*d[LCA] (d[i]是i到根节点的距离))
if(in[x]>in[y]) swap(x,y);
updata(1,1,n,in[x],in[y],z);
return ;
}
树链剖分本质上就是把树剖分成了若干条不相交的链,每条链对应dfs序上一段连续的区间。于是在维护两个点路径上的点权的时候,就不需要单点修改,而是变成了log次区间修改。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 1e5+7;
const ll inf = 34359738370;
int n,m,r,p;
struct
{
int to,next;
}e[maxn<<1];
int head[maxn],num=1;
void add(int v,int u)
{
e[num].to=u;
e[num].next=head[v];
head[v]=num++;
}
int son[maxn],size[maxn],fa[maxn],dep[maxn];
// 重孩子 树的节点个数 父节点 节点深度
void dfs1(int rt,int par)
{
size[rt]=1;
for(int i=head[rt];i;i=e[i].next)
{
int to=e[i].to;
if(to == par) continue;
fa[to]=rt;
dep[to]=dep[rt]+1;
dfs1(to,rt);
size[rt]+=size[to];
if(size[to]>size[son[rt]]) son[rt]=to;
}
}
int top[maxn],pos[maxn],w[maxn],in[maxn],cnt=0;
// 所在链的顶端 dfs序数组 原本的树节点的点权 dfs序的时间戳 时间戳标记
//in[i] 节点i的dfs序 pos[i] dfs序中第i个元素的节点编号
//i-> w[pos[i]] 对这个数组建线段树 维护区间和
void dfs2(int rt,int t)//t是rt所在链的链顶
{
pos[++cnt]=rt;
in[rt]=cnt;
top[rt]=t;
if(son[rt]) dfs2(son[rt],t);//优先访问重链 保证重链节点都在dfs序的一段连续区间内
for(int i=head[rt];i;i=e[i].next)
{
int to=e[i].to;
if(to == fa[rt] || to==son[rt]) continue;
dfs2(to,to);//访问轻链的时候自己(非重孩子)就是轻链的顶端 重孩子按定义不可以是链顶
}
}
//树链刨分
int tree[maxn<<2],tag[maxn<<2];//对dfs序建线段树 维护dfs序数组的区间和
inline int lc(int &rt)
{
return rt<<1;
}
inline int rc(int &rt)
{
return rt<<1|1;
}
inline void pushup(int rt)
{
tree[rt]=(tree[lc(rt)]+tree[rc(rt)])%p;
}
inline void change(int rt,int l,int r,int v)//区间[l,r] +v
{
tree[rt]+=(r-l+1)*v;
tag[rt]+=v;
}
inline void build(int rt,int l,int r)
{
if(l == r)
{
tree[rt]=w[pos[l]];
return ;
}
int mid=(l+r)>>1;
build(lc(rt),l,mid);
build(rc(rt),mid+1,r);
pushup(rt);
}
inline void pushdown(int rt,int l,int r)
{
if(tag[rt])
{
int mid=(l+r)>>1;
change(lc(rt),l,mid,tag[rt]);
change(rc(rt),mid+1,r,tag[rt]);
tag[rt]=0;
}
return ;
}
inline void updata(int rt,int l,int r,int vl,int vr,int v)//区间加v
{
if(l>vr || r<vl) return ;
if(vl<=l && r<=vr)
{
change(rt,l,r,v);
return ;
}
int mid=(l+r)>>1;
pushdown(rt,l,r);
updata(lc(rt),l,mid,vl,vr,v);
updata(rc(rt),mid+1,r,vl,vr,v);
pushup(rt);
}
inline int query(int rt,int l,int r,int vl,int vr)
{
if(l>vr || r<vl) return 0;
if(vl<=l && r<=vr)
{
return tree[rt];
}
int mid=(l+r)>>1;
pushdown(rt,l,r);
return (query(lc(rt),l,mid,vl,vr)+query(rc(rt),mid+1,r,vl,vr))%p;
}
//任意两点之间路上 只能有log条轻链+log条重链
//求LCA过程就是 把链顶深度大的这个点往上跳到链顶 如果还不在同一链就继续循环该过程 直到2个点在同一个链中,深度小的就是LCA
inline void updata_chain(int x,int y,int z)//x y最短路径上的点 点权+z
{
int fx=top[x],fy=top[y];
while(fx != fy)
{
if(dep[fx] < dep[fy]) swap(x,y),swap(fx,fy);//保证fx是链顶最深的点的链顶
updata(1,1,n,in[fx],in[x],z);
x=fa[fx];//fx->x这条链改完了 x变成链顶的父节点 继续修改
fx=top[x];
}
//此时x y在同一条链当中 (深度小的那个点就是原先x、y的LCA 且x,y之间的最短距离就是d[x]+d[y]-2*d[LCA] (d[i]是i到根节点的距离))
if(in[x]>in[y]) swap(x,y);
updata(1,1,n,in[x],in[y],z);
return ;
}
inline int query_chain(int x,int y)
{
int fx=top[x],fy=top[y],ans=0;
while(fx != fy)
{
if(dep[fx] < dep[fy]) swap(x,y),swap(fx,fy);//保证fx是最深点的链顶
ans=(ans+query(1,1,n,in[fx],in[x]))%p;
x=fa[fx];//fx->x这条链查询 x变成链顶的父节点 继续查询
fx=top[x];
}
if(in[x] > in[y]) swap(x,y);
ans=(ans+query(1,1,n,in[x],in[y]))%p;
return ans;
}
int main()
{
scanf("%d %d %d %d",&n,&m,&r,&p);
for(int i=1;i<=n;i++) scanf("%d",w+i);
for(int i=1;i<n;i++)
{
int v,u;
scanf("%d %d",&v,&u);
add(v,u),add(u,v);
}
dfs1(r,r);//求出重孩子
dfs2(r,r);//重链划分+求dfs序
build(1,1,n);
while(m--)
{
int f,x,y,z;
scanf("%d",&f);
if(f == 1)//x y链上都加z
{
scanf("%d %d %d",&x,&y,&z);
updata_chain(x,y,z);
}
else if(f == 2)//查询x y最短路径上的节点点权和
{
scanf("%d %d",&x,&y);
printf("%d\n",query_chain(x,y));
}
else if(f == 3)//修改以x为根的整颗子树
{
scanf("%d %d",&x,&z);
updata(1,1,n,in[x],in[x]+size[x]-1,z);
}
else //查询以x为根的整颗子树
{
scanf("%d",&x);
printf("%d\n",query(1,1,n,in[x],in[x]+size[x]-1));
}
}
return 0;
}