树(重)链剖分
已知一棵包含 N N N 个结点的树,每个节点上包含一个数值,需要支持以下操作:
操作 1 1 1: 格式: 1 1 1 x x x y y y z z z 表示将树从 x x x 到 y y y 结点最短路径上所有节点的值都加上 z z z
操作 2 2 2: 格式: 2 2 2 x x x y y y 表示求树从 x x x 到 y y y 结点最短路径上所有节点的值之和
操作 3 3 3: 格式: 3 3 3 x x x z z z 表示将以 x x x 为根节点的子树内所有节点值都加上 z z z
操作 4 4 4: 格式: 4 4 4 x x x 表示求以 x x x 为根节点的子树内所有节点值之和
一、算法前置知识点
1.
1.
1.树剖法求树上
L
C
A
LCA
LCA.
2.
2.
2.线段树的区间运用.
二、算法流程
1.
1.
1. 做好树剖法求
L
C
A
LCA
LCA 的预处理工作
①
①
①
d
f
s
1
dfs1
dfs1
②
②
②
d
f
s
2
dfs2
dfs2
树剖法求LCA(与本文相连接)
2.
2.
2. 在
1.
1.
1.中的
②
②
② 里:
添加一个
b
a
s
[
i
]
bas[i]
bas[i] 数组,记录树的重链剖分法的
d
f
s
dfs
dfs 序 。
即在
d
f
s
2
dfs2
dfs2 里 首行 添加
b
a
s
[
+
+
b
a
s
[
0
]
]
=
x
bas[++bas[0]] = x
bas[++bas[0]]=x。
(
b
a
s
[
0
]
)
(bas[0])
(bas[0])仅起计数作用。
通过此数组,我们可以得到用剖分法剖树得到的序列。
这样,该树的每一条链里的点在这个序列里都是连续的!
因此可以通过 该序列建立一棵线段树,对原来的树里的链的点 可以进行 区间操作。
3.
3.
3. 建立线段树:
线段树(与此文相连接)
考虑根据什么建立一棵线段树?
我们得到的
b
a
s
[
i
]
bas[i]
bas[i] 数组 只是 按链的点号顺序的序列。我们需要 记录每个点号 出现在 序列的第几个位置。这样才能实现线段树的区间修改。
再建立一个数组
p
o
s
[
b
a
s
[
i
]
]
=
i
pos[bas[i]]=i
pos[bas[i]]=i 表示 点号为
b
a
s
[
i
]
bas[i]
bas[i]的点 在序列中位于
i
i
i 号。
for(int i = 1; i <= n; i++) pos[bas[i]] = i;
然后 在对 b a s [ i ] bas[i] bas[i] 序列 建立一颗线段树,线段树保存的 每个点号对应的权值。
tree[cur] = f[bas[l]];
其余线段树内容不变。
4.
4.
4. 查询和修改的变化:
在修改
x
x
x、
y
y
y路径上的点的权值时,由于是树剖法求
L
C
A
LCA
LCA :
一定有一个点从
当
前
点
当前点
当前点 跳至
当
前
点
的
链
头
的
父
亲
所
在
点
当前点的链头的父亲 所在点
当前点的链头的父亲所在点。
而途径的所有点,在
b
a
s
[
i
]
bas[i]
bas[i] 是连续的,我们通过
p
o
s
[
i
]
pos[i]
pos[i] 已经确定了这些点在线段树中是连续的,就可以用线段树对这些点统一做
修
改
修改
修改操作。
而同理查询:
在查询
x
x
x、
y
y
y路径上的点的权值和时,由于是树剖法求
L
C
A
LCA
LCA :
一定有一个点从
当
前
点
当前点
当前点 跳至
当
前
点
的
链
头
的
父
亲
所
在
点
当前点的链头的父亲 所在点
当前点的链头的父亲所在点。
而途径的所有点,在
b
a
s
[
i
]
bas[i]
bas[i] 是连续的,我们通过
p
o
s
[
i
]
pos[i]
pos[i] 已经确定了这些点在线段树中是连续的,就可以用线段树对这些点统一做
求
和
求和
求和操作。
而查询或修改
x
x
x 点子树的点的权值和时,由于
d
f
s
dfs
dfs 序的性质,点
x
x
x 与 其子树上的点 在
b
a
s
[
i
]
bas[i]
bas[i] 数组中也是连续的。
因此可以用线段树做修改和查询操作。变化参照上边的查询和修改。
查询和查询的范围是
[
p
o
s
[
x
]
,
p
o
s
[
x
]
+
s
i
z
e
[
x
]
−
1
]
[pos[x],pos[x]+size[x]-1]
[pos[x],pos[x]+size[x]−1]。
完整代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <cstdlib>
using namespace std;
#define N 100010
#define M 100010
int n,m,s,p,ans;
int f[N],head[N],tree[N<<2],bas[N],top[N],lazy[N<<2],pos[N];
int deep[N],size[N],fa[N],son[N];
struct list
{
int to,nxt;
}e[M<<1];
int read()
{
int rt = 0, in = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') in = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {rt = rt * 10 + ch - '0'; ch = getchar();}
return rt * in;
}
void add_edge(int u, int v)
{
e[++head[0]].to = v;
e[head[0]].nxt = head[u];
head[u] = head[0];
}
int dfs1(int x)
{
size[x] = 1;
for(int i = head[x]; i; i = e[i].nxt)
{
if(deep[e[i].to]) continue;
deep[e[i].to] = deep[x] + 1;
fa[e[i].to] = x;
size[x] += dfs1(e[i].to);
if(size[son[x]] < size[e[i].to]) son[x] = e[i].to;
}
return size[x];
}
void dfs2(int x, int root)
{
bas[++bas[0]] = x, top[x] = root;
if(son[x]) dfs2(son[x], root);
for(int i = head[x]; i; i = e[i].nxt)
if(e[i].to != son[x] && e[i].to != fa[x])
dfs2(e[i].to, e[i].to);
}
void build_tree(int cur, int l, int r)
{
if(l == r)
{
tree[cur] = f[bas[l]];
return;
}
int mid = l + r >> 1;
build_tree(cur<<1, l, mid);
build_tree(cur<<1|1, mid+1, r);
tree[cur] = (tree[cur<<1] + tree[cur<<1|1]) % p;
}
void pushdown(int cur, int l, int r)
{
int mid = l + r >> 1;
tree[cur<<1] = (tree[cur<<1] + (mid-l+1) * lazy[cur]) % p;
tree[cur<<1|1] = (tree[cur<<1|1] + (r-mid) * lazy[cur]) % p;
lazy[cur<<1] += lazy[cur];
lazy[cur<<1|1] += lazy[cur];
lazy[cur] = 0;
}
void update(int cur, int l, int r, int L, int R, int w)
{
if(L <= l && r <= R)
{
tree[cur] = (tree[cur] + (r-l+1) * w) % p;
lazy[cur] += w;
return;
}
if(lazy[cur]) pushdown(cur, l, r);
int mid = l + r >> 1;
if(R <= mid) update(cur<<1, l, mid, L, R, w);
else if(L > mid) update(cur<<1|1, mid+1, r, L, R, w);
else update(cur<<1, l, mid, L, mid, w), update(cur<<1|1, mid+1, r, mid+1, R, w);
tree[cur] = (tree[cur<<1] + tree[cur<<1|1]) % p;
}
void query_sum(int cur, int l, int r, int L, int R)
{
if(L <= l && r <= R)
{
ans = (ans + tree[cur]) % p;
return;
}
if(lazy[cur]) pushdown(cur, l, r);
int mid = l + r >> 1;
if(R <= mid) query_sum(cur<<1, l, mid, L, R);
else if(L > mid) query_sum(cur<<1|1, mid+1, r, L, R);
else query_sum(cur<<1, l, mid, L, mid), query_sum(cur<<1|1, mid+1, r, mid+1, R);
tree[cur] = tree[cur<<1] + tree[cur<<1|1];
}
void query_lca(int u, int v, int w, int ins)
{
while(top[u] != top[v])
{
if(deep[top[u]] > deep[top[v]]) swap(u, v);
if(ins == 1) update(1, 1, n, pos[top[v]], pos[v], w);
if(ins == 2) query_sum(1, 1, n, pos[top[v]], pos[v]);
v = fa[top[v]];
}
if(deep[u] > deep[v]) swap(u, v);
if(ins == 1) update(1, 1, n, pos[u], pos[v], w);
if(ins == 2) query_sum(1, 1, n, pos[u], pos[v]);
}
int main()
{
n = read(), m = read(), s = read(), p = read();
for(int i = 1; i <= n; i++) f[i] = read();
for(int i = 1; i < n; i++)
{
int u = read(), v = read();
add_edge(u,v), add_edge(v,u);
}
deep[s] = 1;
dfs1(s);
dfs2(s, s);
for(int i = 1; i <= n; i++) pos[bas[i]] = i;
build_tree(1, 1, n);
for(int i = 1; i <= m; i++)
{
int ins = read();
if(ins == 1)
{
int l = read(), r = read(), w = read();
query_lca(l, r, w, 1);
}
else if(ins == 2)
{
ans = 0;
int l = read(), r = read();
query_lca(l, r, 0, 2);
printf("%d\n",ans%p);
}
else if(ins == 3)
{
int x = read(), w = read();
update(1, 1, n, pos[x], pos[x]+size[x]-1, w);
}
else if(ins == 4)
{
ans = 0;
int x = read();
query_sum(1, 1, n, pos[x], pos[x]+size[x]-1);
printf("%d\n",ans%p);
}
}
system("pause");
return 0;
}
总结:重链剖分 = 树剖求LCA + 线段树 + bas[i] + pos[i]
例题:
[模版]重链剖分
luogu P3258
luogu P2680