前置知识 1
前置知识 2
(一些促进理解图可以看一下别人的)
# 树剖:
前置基础:DFS 序, 树形 DP,线段树,链式前向星……
树链剖分 就是对一棵树分成几条链,把树形变为线性,减少处理难度
需要处理的问题:
- 将树从 x 到 y 结点最短路径上所有节点的值都加上 z
- 求树从 x 到 y 结点最短路径上所有节点的值之和
- 将以 x 为根节点的子树内所有节点值都加上 z
- 求以 x 为根节点的子树内所有节点值之和
# 概念:
- 重儿子:对于每一个非叶子节点 (x),在它的儿子 (y,z……) 中 以儿子 (y,z……) 为根的子树大小最大的节点 (y,z…… 中的一个) 为该节点的重儿子 (PS: 若每个儿子的子树大小相同那么随意让一个儿子为该节点的重儿子)
- 轻儿子:对于每一个非叶子节点,它的儿子中 非重儿子 的剩下所有儿子即为轻儿子
- 叶子节点没有重儿子也没有轻儿子(因为它没有儿子。。)
- 重边:一个父亲连接他的重儿子的边称为重边 // 原写法:连接任意两个重儿子的边叫做重边
- 轻边:剩下的即为轻边
- 重链:相邻重边连起来的 连接一条重儿子 的链叫重链
对于叶子节点,若其为轻儿子,则有一条以自己为起点的长度为 1 的链
每一条重链以轻儿子为起点
Code:
#include<bitsdc++.h> | |
using namespace std; | |
const long long MAXN = 1e5+5; | |
struct edge{ | |
long long nxt,to; | |
edge(long long a1=0,long long a2=0){ | |
nxt=a1; | |
to=a2; | |
} | |
}; | |
long long n,m,root,tot,cnt,MOD,lazy[4 * MAXN],w[MAXN],dep[MAXN],fa[MAXN],size[MAXN],son[MAXN],top[MAXN],id[MAXN],val[MAXN],head[MAXN],tree[4 * MAXN]; | |
edge e[4 * MAXN]; | |
void add_edge(long long from,long long to){// 链式前向星记录 | |
e[++tot] = edge(head[from],to); | |
head[from] = tot; | |
} | |
// 两遍 dfs | |
void dfs1(long long now,long long f){ | |
long long maxn=0;// 记重儿子 | |
fa[now] = f;// 标记父亲 | |
dep[now] = dep[f] + 1;// 记住该点的深度 | |
size[now] = 1;// 该点的子树大小,需要包括自己 | |
for(long long i=head[now]; i; i=e[i].nxt){// 遍历 | |
long long to = e[i].to; | |
if(to == f)// 若为父亲则就 continue | |
continue; | |
dfs1(to,now);//dfs 儿子,方便后面算子树大小 | |
if(!maxn || size[maxn] < size[to])// 如果当时 maxn 没被赋值(有可能是第一个,也有可能该点只有一个儿子) | |
// 如果现在这个儿子为节点的子树大小比原本标记的大那么就代替 | |
maxn = to;// 代替 | |
size[now] += size[to];// 加上儿子的子树大小 | |
} | |
son[now] = maxn;// 存 now 这个点的重儿子 | |
} | |
void dfs2(long long now,long long topf){//now 为当前节点,topf 为当前链的链顶 (链最顶端的节点) | |
id[now] = ++cnt;// 给该点个编号 | |
val[cnt] = w[now];// 把原本的值赋值在新编号上 | |
top[now] = topf;//top [] 为该节点所在链的顶端位置 | |
if(!son[now]) return;// 如果没有重儿子(儿子) 那么就返回 | |
dfs2(son[now],topf);// 先处理重儿子再处理轻儿子 | |
for(long long i=head[now]; i ; i=e[i].nxt){// 遍历 | |
long long to = e[i].to; | |
if(to == fa[now] || to == son[now])// 如果为父亲或者遍历到重儿子就 continue,重儿子我们是最先 dfs 的 | |
continue; | |
dfs2(to,to);// 对轻儿子进行处理 | |
} | |
} | |
// 线段树: | |
void update(long long k){// 合并 | |
tree[k] = tree[k<<1] + tree[k<<1|1]; | |
tree[k] %= MOD; | |
} | |
void spread_lazy(long long l,long long r,long long k){ | |
if(lazy[k]){ | |
long long mid = (l+r)>>1; | |
lazy[k<<1] += lazy[k];lazy[k<<1|1] += lazy[k]; | |
tree[k<<1] += (mid-l+1) * lazy[k];tree[k<<1|1] += (r-mid) * lazy[k]; | |
tree[k<<1] %= MOD;tree[k<<1|1] %= MOD; | |
lazy[k<<1]%=MOD;lazy[k<<1|1]%=MOD;lazy[k]=0; | |
}// 不稍微压压行没灵魂 | |
} | |
void build_tree(long long now_l,long long now_r,long long k){// 经典的建树环节 | |
if(now_l == now_r){ | |
tree[k] = val[now_l] % MOD; | |
return; | |
} | |
long long mid = (now_l+now_r)>>1; | |
build_tree(now_l,mid,k<<1); | |
build_tree(mid+1,now_r,k<<1|1); | |
update(k);// 待会讲解一下 update 环节 | |
} | |
void add(long long now_l,long long now_r,long long k,long long l,long long r,long long q){// 区间加 | |
if(l <= now_l && r >= now_r){ | |
tree[k] += (now_r-now_l+1) * q; | |
tree[k]%=MOD; | |
lazy[k] += q;// 传递懒标记 | |
lazy[k]%=MOD;// 不要忘记取模 | |
return; | |
} | |
spread_lazy(now_l,now_r,k);// 读取懒标记 | |
long long mid = (now_l+now_r)>>1; | |
if(l <= mid) add(now_l,mid,k<<1,l,r,q); | |
if(r > mid) add(mid+1,now_r,k<<1|1,l,r,q); | |
update(k);// 合并 | |
} | |
long long query(long long now_l,long long now_r,long long k,long long l,long long r){// 区间查询 线段树 | |
if(l <= now_l && r>=now_r) | |
return tree[k] % MOD; | |
spread_lazy(now_l,now_r,k); | |
long long mid = (now_l+now_r)>>1,ans=0; | |
if(l <= mid) ans+=query(now_l,mid,k<<1,l,r); | |
if(r > mid) ans+=query(mid+1,now_r,k<<1|1,l,r); | |
return ans % MOD; | |
} | |
long long Ask_Range(long long x,long long y){ // 区间查询 树剖 | |
long long ans = 0; | |
while(top[x] != top[y]){ // 不在同一条链上 | |
if(dep[top[x]] < dep[top[y]]) swap(x,y);// 始终让 x 为深度大的 | |
ans += query(1,n,1,id[top[x]],id[x]); | |
ans%=MOD; | |
x = fa[top[x]]; | |
} | |
if(dep[x] > dep[y]) | |
swap(x,y); | |
ans += query(1,n,1,id[x],id[y]); | |
return ans % MOD; | |
} | |
long long Ask_Son(long long x){ // 查询该节点以及其的子树 | |
return query(1,n,1,id[x],id[x]+size[x]-1); // 编号是连续的,so 起始位置是 x 终止位置是 id [x]+size [x]-1,把 x 减去 | |
} | |
void Update_Range(long long x,long long y,long long z){ // 区间修改 可能 dfs 序不连续 | |
while(top[x] != top[y]){ // 不在同一条链上 | |
if(dep[top[x]] < dep[top[y]]){ | |
swap(x,y); | |
} | |
// ans += query(1,n,1,id[top[x]],id[x]); | |
add(1,n,1,id[top[x]],id[x],z); | |
// ans%=mod; | |
x = fa[top[x]]; | |
} | |
if(dep[x] > dep[y]) | |
swap(x,y); | |
// ans += query(1,n,1,id[x],id[y]); | |
add(1,n,1,id[x],id[y],z); | |
} | |
void Update_Son(long long x,long long z){ // 修改该节点以及其的子树 | |
add(1,n,1,id[x],id[x] + size[x] - 1,z);// 同一棵子树的 dfs 序是连续的,深度小的 dfs 序小就在前边 | |
} | |
int main(){ | |
scanf("%lld%lld%lld%lld",&n,&m,&root,&MOD); | |
for(long long i=1; i<=n; i++){ | |
// cin>>w[i]; | |
scanf("%lld",&w[i]); | |
} | |
for(long long i=1; i<n; i++){ | |
long long from,to; | |
// cin>>from>>to; | |
scanf("%lld%lld",&from,&to); | |
add_edge(from,to); | |
add_edge(to,from); | |
} | |
dfs1(root,0); | |
dfs2(root,0); | |
build_tree(1,n,1); | |
for(long long i=1; i<=m; i++){ | |
long long op,x,y,z; | |
cin>>op; | |
if(op==1){// 剩下的就是题目要求的 | |
cin>>x>>y>>z; | |
Update_Range(x,y,z); | |
} | |
else if(op == 2){ | |
cin>>x>>y; | |
printf("%lld\n",Ask_Range(x,y)); | |
} | |
else if(op == 3){ | |
cin>>x>>z; | |
Update_Son(x,z); | |
} | |
else if(op == 4){ | |
cin>>x; | |
printf("%lld\n",Ask_Son(x)); | |
} | |
} | |
return 0; | |
} |