题意:
已知一棵包含 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为根节点的子树内所有节点值之和
分析:
我的理解:
显然我们可以
l
c
a
lca
lca暴力求解,只不过会超时。
那么树链剖分登场了。
树链剖分将树的点分为轻点(轻儿子)和重点(重儿子),将树边分为轻边和重边,以及重链,再利用一些维护区间的数据结构有效的解决了这个问题。
重儿子:非叶节点中,这个节点的儿子中,子树节点最多的 那个儿子 称为重儿子。
轻儿子:不是重儿子就是轻儿子。
重边:父亲节点连接重儿子的边。
轻边:非重即轻。
重链:相邻重边连在一起形成的链。
轻链:非重即轻
对于叶子节点,若其为轻儿子,则有一条以自己为起点的长度为1的链。
每一条重链以轻儿子为起点。
然后就说一下怎么实现吧(简述原理),
I
.
I.
I.
d
f
s
1
dfs1
dfs1第一次得到这棵树的重儿子、轻儿子、节点的父亲节点、节点的深度。
void dfs1(int x, int f, int d){
pre[x] = f;
deep[x] = d;
size[x] = 1;
int MAX = -1;
for(int i = head[x]; i; i = edge[i].next){
int v = edge[i].to;
if(v == f) continue;
dfs1(v, x, d + 1);
size[x] += size[v];
if(size[v] > MAX) MAX = size[v], son[x] = v;
}
}
I I . II. II. d f s 2 dfs2 dfs2第二次得到映射的新点编号、新点的权值、每个点所在链的顶端那个点。
void dfs2(int x, int topf){
id[x] = ++tot;
nw[tot] = w[x];
top[x] = topf;
if(!son[x]) return ;
dfs2(son[x], topf);
for(int i = head[x]; i; i = edge[i].next){
int v = edge[i].to;
if(v == pre[x] || v == son[x]) continue;
dfs2(v, v);
}
}
d
f
s
2
dfs2
dfs2时我们
d
f
s
dfs
dfs的顺序是先重儿子再轻儿子,这样得到了可以用数据结构维护的区间。
因为我们是先重后轻,所以得到的重链一定是连续的,新子树的编号也是连续的(所以可以区间维护),这个可以自己画图比划一下,
d
f
s
dfs
dfs而已。
怎么来维护任意两点的路径:
假设
x
,
y
x,y
x,y两点,我们令
x
x
x为链顶端那个点较深的那个点。
然后我认为和倍增求
l
c
a
lca
lca有点相似,
1.
1.
1.先处理
x
x
x到
x
x
x链顶端点的区间,
2.
2.
2.然后让
x
x
x点跳到
x
x
x链顶端点上面一个点,
3.
3.
3.反复,直至
x
x
x的链顶端点和
y
y
y相同。
4.
4.
4.现在
x
x
x和
y
y
y在一条链上,直接求他们的区间和即可。
w
[
i
]
w[i]
w[i]表示
i
i
i节点权值
d
e
e
p
[
i
]
deep[i]
deep[i]表示
i
i
i节点深度
p
r
e
[
i
]
pre[i]
pre[i]表示
i
i
i节点的父亲
t
o
p
[
i
]
top[i]
top[i]表示
i
i
i节点的链顶端点
s
o
n
[
i
]
son[i]
son[i]表示
i
i
i节点的重儿子
s
i
z
e
[
i
]
size[i]
size[i]
i
i
i节点的子树节点个数
i
d
[
i
]
id[i]
id[i]表示
i
i
i节点的新编号
n
w
[
i
]
nw[i]
nw[i]表示
i
i
i新编号节点的权值
int queryDis(int x, int y){
int ans = 0;
while(top[x] != top[y]){
if(deep[top[x]] < deep[top[y]]) swap(x, y);
ans += query(1, 1, n, id[top[x]], id[x]);
ans %= p;
x = pre[top[x]];
}
if(deep[x] > deep[y]) swap(x, y);
ans += query(1, 1, n, id[x], id[y]);
ans %= p;
return ans;
}
区间修改怎么操作?和区间查询一模一样。
void updateDis(int x, int y, int z){
while(top[x] != top[y]){
if(deep[top[x]] < deep[top[y]]) swap(x, y);
update(1, 1, n, id[top[x]], id[x], z);
x = pre[top[x]];
}
if(deep[x] > deep[y]) swap(x, y);
update(1, 1, n, id[x], id[y], z);
}
如何询问子树的和?我们知道子树的新编号是连续的,所以
int querySon(int x){
return query(1, 1, n, id[x], id[x] + size[x] - 1) % p;
}
修改子树也是同理,
void updateSon(int x, int z){
update(1, 1, n, id[x], id[x] + size[x] - 1, z);
}
总的代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> pii;
const int maxn = 2e5 + 5;
const int maxm = 100 + 5;
const int inf = 0x3f3f3f3f;
const LL mod = 1e9 + 7;//19260817
const double pi = acos(-1.0);
int n, m, r, p, u, v, cnt, tot, opt, x, y, z;
int w[maxn], head[maxn];
int deep[maxn], pre[maxn], top[maxn], son[maxn], size[maxn], id[maxn], nw[maxn];//树剖
struct qwq{
int w, l, r, lazy;
int len(){
return r - l + 1;
}
}a[maxn << 2];
struct node{
int to, next;
}edge[maxn << 1];
void addedge(int u, int v){
edge[++cnt].to = v;
edge[cnt].next = head[u];
head[u] = cnt;
}
void pushup(int k){
a[k].w = (a[k << 1].w + a[k << 1 | 1].w) % p;
}
void pushdown(int k){
if(a[k].lazy){
a[k << 1].lazy += a[k].lazy;
a[k << 1 | 1].lazy += a[k].lazy;
a[k << 1].w += a[k << 1].len() * a[k].lazy;
a[k << 1 | 1].w += a[k << 1 | 1].len() * a[k].lazy;
a[k << 1].w %= p;
a[k << 1 | 1].w %= p;
a[k].lazy = 0;
}
}
void build(int k, int l, int r){
a[k].l = l, a[k].r = r;
if(l == r){
a[k].w = nw[l] % p;
return ;
}
int mid = (l + r) >> 1;
build(k << 1, l, mid);
build(k << 1 | 1, mid + 1, r);
pushup(k);
}
void update(int k, int l, int r, int x, int y, int z){
if(l >= x && r <= y){
a[k].lazy += z;
a[k].w = (a[k].w % p + z * a[k].len() % p) % p;
return ;
}
pushdown(k);
int mid = (l + r) >> 1;
if(x <= mid) update(k << 1, l, mid, x, y, z);
if(y > mid) update(k << 1 | 1, mid + 1, r, x, y, z);
pushup(k);
}
int query(int k, int l, int r, int x, int y){
if(l >= x && r <= y){
return a[k].w % p;
}
pushdown(k);
int mid = (l + r) >> 1, ans = 0;
if(x <= mid) ans = (ans + query(k << 1, l, mid, x, y)) % p;
if(y > mid) ans = (ans + query(k << 1 | 1, mid + 1, r, x, y)) % p;
return ans % p;
}
void dfs1(int x, int f, int d){
pre[x] = f;
deep[x] = d;
size[x] = 1;
int MAX = -1;
for(int i = head[x]; i; i = edge[i].next){
int v = edge[i].to;
if(v == f) continue;
dfs1(v, x, d + 1);
size[x] += size[v];
if(size[v] > MAX) MAX = size[v], son[x] = v;
}
}
void dfs2(int x, int topf){
id[x] = ++tot;
nw[tot] = w[x];
top[x] = topf;
if(!son[x]) return ;
dfs2(son[x], topf);
for(int i = head[x]; i; i = edge[i].next){
int v = edge[i].to;
if(v == pre[x] || v == son[x]) continue;
dfs2(v, v);
}
}
int queryDis(int x, int y){
int ans = 0;
while(top[x] != top[y]){
if(deep[top[x]] < deep[top[y]]) swap(x, y);
ans += query(1, 1, n, id[top[x]], id[x]);
ans %= p;
x = pre[top[x]];
}
if(deep[x] > deep[y]) swap(x, y);
ans += query(1, 1, n, id[x], id[y]);
ans %= p;
return ans;
}
void updateDis(int x, int y, int z){
while(top[x] != top[y]){
if(deep[top[x]] < deep[top[y]]) swap(x, y);
update(1, 1, n, id[top[x]], id[x], z);
x = pre[top[x]];
}
if(deep[x] > deep[y]) swap(x, y);
update(1, 1, n, id[x], id[y], z);
}
int querySon(int x){
return query(1, 1, n, id[x], id[x] + size[x] - 1) % p;
}
void updateSon(int x, int z){
update(1, 1, n, id[x], id[x] + size[x] - 1, z);
}
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 - 1; i++){
scanf("%d %d", &u, &v);
addedge(u, v), addedge(v, u);
}
dfs1(r, -1, 1);
dfs2(r, r);
build(1, 1, n);
while(m--){
scanf("%d", &opt);
if(opt == 1){
scanf("%d %d %d", &x, &y, &z);
updateDis(x, y, z);
}else if(opt == 2){
scanf("%d %d", &x, &y);
printf("%d\n", queryDis(x, y) % p);
}else if(opt == 3){
scanf("%d %d", &x, &z);
updateSon(x, z);
}else if(opt == 4){
scanf("%d", &x);
printf("%d\n", querySon(x) % p);
}
}
return 0;
}
本人初学树链剖分,以上内容是我看了几篇博客自己的总结,有些借鉴。我讲的十分粗略,等我基本掌握了树剖的精髓会来继续更新博客。
我认为树链剖分得结合图来学,推荐几篇博客:
树链剖分详解
浅谈树链剖分