写在前面:强烈建议初学的同学如果不理解的话先把代码写一遍,抄一遍也行(像我一样),非常有助于理解
概念:
-
重儿子: 一个节点所有儿子中最大的儿子
-
轻儿子: 一个节点除重儿子之外的其他儿子
特别地,叶子节点既没有重儿子也没有轻儿子
-
重链: 重儿子连接形成的链叫重链
主要思想:
把一颗树拆成许多条链,把树上操作改为链上操作(区间操作),并利用线段树或树状数组等数据结构维护,以降低时间复杂度
每次进行操作时,先修改重链,再修改轻链
常见操作:
-
将树从 (x) 到 (y) 结点最短路径上所有节点的值都加上 (z)
-
求树从 (x) 到 (y) 结点最短路径上所有节点的值之和
-
将以 (x) 为根节点的子树内所有节点值都加上 (z)
-
求以 (x) 为根节点的子树内所有节点值之和
代码分析:
dfs1
(dfs1) 要预处理出以下内容:
-
(fa[x]:) 节点 (x) 的父亲。
-
(dep[x]:) 节点 (x) 的深度。
-
(siz[x]:) 以节点 (x) 为根的子树大小
-
(son[x]:) 节点 (x) 的重儿子编号
void dfs1(int x, int f){
fa[x] = f;
dep[x] = dep[f] + 1;
siz[x] = 1;
for(int i = head[x]; i; i = edge[i].nxt){
int y = edge[i].v;
if(y == f) continue;
dfs1(y, x);
siz[x] += siz[y];
if(siz[y] > siz[son[x]]) son[x] = y;
}
}
dfs2
(dfs2) 也要预处理一些数据:
-
(top[x]:) 节点 (x) 所在重链的顶端
-
(id[x]:) 把树改为链后节点 (x) 新的编号
-
(tw[cnt]:) 把节点 (x) 的权值存到新的编号中
void dfs2(int x, int topfa){
top[x] = topfa;
id[x] = ++cnt;
tw[cnt] = w[x];
if(!son[x]) return;
dfs2(son[x], topfa);
for(int i = head[x]; i; i = edge[i].nxt){
int y = edge[i].v;
if(y == fa[x] || y == son[x]) continue;
dfs2(y, y);
}
}
线段树部分
(包括 (pushup),(pushdown),(build),(update),(query))
就是普通的线段树维护区间加,区间查询
就不多讲了
void pushup(int rt){
sum[rt] = (sum[ls] + sum[rs]) % mod;
}
void pushdown(int l, int r, int rt){
if(lazy[rt]){
int mid = (l + r) >> 1;
sum[ls] = (sum[ls] + lazy[rt] * (mid - l + 1) % mod) % mod;
sum[rs] = (sum[rs] + lazy[rt] * (r - mid) % mod) % mod;
lazy[ls] = (lazy[ls] + lazy[rt]) % mod;
lazy[rs] = (lazy[rs] + lazy[rt]) % mod;
lazy[rt] = 0;
}
}
void build(int l, int r, int rt){
if(l == r){
sum[rt] = tw[l] % mod;
return;
}
int mid = (l + r) >> 1;
build(l, mid, ls);
build(mid + 1, r, rs);
pushup(rt);
}
void update(int L, int R, int k, int l, int r, int rt){
if(L <= l && r <= R){
sum[rt] = (sum[rt] + k * (r -l + 1) % mod) % mod;
lazy[rt] = (lazy[rt] + k) % mod;
return;
}
pushdown(l, r, rt);
int mid = (l + r) >> 1;
if(L <= mid) update(L, R, k, l, mid, ls);
if(R > mid) update(L, R, k, mid + 1, r, rs);
pushup(rt);
}
int query(int L, int R, int l, int r, int rt){
if(L <= l && r <= R)
return sum[rt];
pushdown(l, r, rt);
int mid = (l + r) >> 1;
int res = 0;
if(L <= mid) res = (res + query(L, R, l, mid, ls)) % mod;
if(R > mid) res = (res + query(L, R, mid + 1, r, rs)) % mod;
return res;
}
update_Range
(链上修改)
这里是重点
因为一条重链上的点的编号一定是连续的,所以重链上的点相当于是一段区间,我们可以利用线段树来进行区间维护及查询,以减少时间复杂度
具体看代码
void update_Range(int x, int y, int k){
k %= mod;
while(top[x] != top[y]){ //x和y不在同一条重链上
if(dep[top[x]] < dep[top[y]]) swap(x, y); //令深度大的点往上跳
update(id[top[x]], id[x], k, 1, n, 1);
x = fa[top[x]]; //然后自己跳到当前重链的父节点上(进入下一条重链)
}
if(id[x] > id[y]) swap(x, y); //这时两个点已经在同一条重链上
update(id[x], id[y], k, 1, n, 1); //直接update两个点之间的点就好
}
query_Range
(链上查询)
与链上修改一个道理,直接看代码吧
//查询同理
int query_Range(int x, int y){
int res = 0;
while(top[x] != top[y]){
if(dep[top[x]] < dep[top[y]]) swap(x, y);
res = (res + query(id[top[x]], id[x], 1, n, 1)) % mod;
x = fa[top[x]];
}
if(id[x] > id[y]) swap(x, y);
res = (res + query(id[x], id[y], 1, n, 1)) % mod;
return res;
}
update_Son
(子树修改)
根据深搜的性质,一棵子树中的点的编号也一定是连续的,所以……
void update_Son(int x, int k){
update(id[x], id[x] + siz[x] - 1, k, 1, n, 1); //这里区间修改的右端点就是id[x] + siz[x] - 1
}
query_Son
(子树查询)
这里跟子树修改一样
int query_Son(int x){
return query(id[x], id[x] + siz[x] - 1, 1, n, 1);
}
完整代码
#include <iostream>
#define ls rt << 1
#define rs (rt << 1) | 1
using namespace std;
const int N = 1e5 + 10;
struct node{
int v, nxt;
}edge[N << 1];
int head[N], tot;
int n, m, root, mod, w[N];
int dep[N], fa[N], siz[N], son[N]; //dfs1需维护的
int top[N], id[N], tw[N], cnt; //dfs2需维护的
int sum[N << 2], lazy[N << 2]; //经典线段树
inline int read(){
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x * f;
}
inline void add(int x, int y){
edge[++tot] = (node) {y, head[x]};
head[x] = tot;
}
//------------------------------------------------以下是dfs预处理
void dfs1(int x, int f){
fa[x] = f;
dep[x] = dep[f] + 1;
siz[x] = 1;
for(int i = head[x]; i; i = edge[i].nxt){
int y = edge[i].v;
if(y == f) continue;
dfs1(y, x);
siz[x] += siz[y];
if(siz[y] > siz[son[x]]) son[x] = y;
}
}
void dfs2(int x, int topfa){
top[x] = topfa;
id[x] = ++cnt;
tw[cnt] = w[x];
if(!son[x]) return;
dfs2(son[x], topfa);
for(int i = head[x]; i; i = edge[i].nxt){
int y = edge[i].v;
if(y == fa[x] || y == son[x]) continue;
dfs2(y, y);
}
}
//------------------------------------------------以下是线段树
void pushup(int rt){
sum[rt] = (sum[ls] + sum[rs]) % mod;
}
void pushdown(int l, int r, int rt){
if(lazy[rt]){
int mid = (l + r) >> 1;
sum[ls] = (sum[ls] + lazy[rt] * (mid - l + 1) % mod) % mod;
sum[rs] = (sum[rs] + lazy[rt] * (r - mid) % mod) % mod;
lazy[ls] = (lazy[ls] + lazy[rt]) % mod;
lazy[rs] = (lazy[rs] + lazy[rt]) % mod;
lazy[rt] = 0;
}
}
void build(int l, int r, int rt){
if(l == r){
sum[rt] = tw[l] % mod;
return;
}
int mid = (l + r) >> 1;
build(l, mid, ls);
build(mid + 1, r, rs);
pushup(rt);
}
void update(int L, int R, int k, int l, int r, int rt){
if(L <= l && r <= R){
sum[rt] = (sum[rt] + k * (r -l + 1) % mod) % mod;
lazy[rt] = (lazy[rt] + k) % mod;
return;
}
pushdown(l, r, rt);
int mid = (l + r) >> 1;
if(L <= mid) update(L, R, k, l, mid, ls);
if(R > mid) update(L, R, k, mid + 1, r, rs);
pushup(rt);
}
int query(int L, int R, int l, int r, int rt){
if(L <= l && r <= R)
return sum[rt];
pushdown(l, r, rt);
int mid = (l + r) >> 1;
int res = 0;
if(L <= mid) res = (res + query(L, R, l, mid, ls)) % mod;
if(R > mid) res = (res + query(L, R, mid + 1, r, rs)) % mod;
return res;
}
//------------------------------------------------以下是链上修改及查询
void update_Range(int x, int y, int k){
k %= mod;
while(top[x] != top[y]){
if(dep[top[x]] < dep[top[y]]) swap(x, y);
update(id[top[x]], id[x], k, 1, n, 1);
x = fa[top[x]];
}
if(id[x] > id[y]) swap(x, y);
update(id[x], id[y], k, 1, n, 1);
}
int query_Range(int x, int y){
int res = 0;
while(top[x] != top[y]){
if(dep[top[x]] < dep[top[y]]) swap(x, y);
res = (res + query(id[top[x]], id[x], 1, n, 1)) % mod;
x = fa[top[x]];
}
if(id[x] > id[y]) swap(x, y);
res = (res + query(id[x], id[y], 1, n, 1)) % mod;
return res;
}
//------------------------------------------------以下是子树上修改及查询
void update_Son(int x, int k){
update(id[x], id[x] + siz[x] - 1, k, 1, n, 1);
}
int query_Son(int x){
return query(id[x], id[x] + siz[x] - 1, 1, n, 1);
}
int main(){
n = read(), m = read(), root = read(), mod = read();
for(int i = 1; i <= n; i++)
w[i] = read();
for(int i = 1; i < n; i++){
int u, v;
u = read(), v = read();
add(u, v), add(v, u);
}
dfs1(root, 0);
dfs2(root, root);
build(1, n, 1);
while(m--){
int op, x, y, k;
op = read();
if(op == 1){
x = read(), y = read(), k = read();
update_Range(x, y, k);
}else if(op == 2){
x = read(), y = read();
printf("%d
", query_Range(x, y));
}else if(op == 3){
x = read(), k = read();
update_Son(x, k);
}else{
x = read();
printf("%d
", query_Son(x));
}
}
return 0;
}