震惊!wyj 的线段树模板居然是错的!
这个是 线段树模板 …
如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:
操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z
操作2: 格式: 2 x y 表示求树从x到y结点最短路径上所有节点的值之和
操作3: 格式: 3 x z 表示将以x为根节点的子树内所有节点值都加上z
操作4: 格式: 4 x 表示求以x为根节点的子树内所有节点值之和
考虑用一些数据结构,但是显然这里线段树什么的是不合理的,因为它不是一条链。
但是我们可以把一棵树砍成几条链,连起来组成一条长链。
这就是树链剖分的基本原理。
剖的方法: 轻重边划分
size[x]
定义为以 x 为子树的节点个数。
重儿子:当前节点下 size 值最大的儿子(若有 size 相等则随便连一个)。
重边:连接当前节点和其重孩子的边。
轻边:连接当前节点和除了重孩子的边。
需要使用到的变量有:
tim // 时间戳,对应的当前的链上的位置
size[] // 当前节点的 size 数
son[] // 记录当前节点的重儿子
fa[] // 记录当前节点的父亲
dep[] // 当前节点的深度
top[] // 当前重链的最顶部(距离根节点最近的点)
tid[] // 到达当前节点的时间,即当前节点在线段树中的编号
Rank[] // 当前时间对应着哪个节点,即当前节点在线段树中的编号对应在原树中的编号
在树链剖分时,首先我们要确定每一个节点的
size
从而确定它的重儿子,然后连成重边。
之后我们再把重边都连起来拉成重链。
这个过程需要两遍
dfs
。
int fa[N], dep[N], size[N], son[N];
void dfs1(int u, int f, int depth) {
fa[u] = f;
dep[u] = depth;
size[u] = 1;
for(int i = head[u]; i; i = e[i].next) {
int v = e[i].to;
if(v != fa[u]) {
dfs1(v, u, depth + 1);
size[u] += size[v];
if(son[u] == -1 || size[son[u]] < size[v])
son[u] = v;
}
}
}
int tim = 0;
int top[N], tid[N], Rank[N];
void dfs2(int u, int tp) {
top[u] = tp;
tid[u] = ++ tim;
Rank[tim] = u;
if(son[u] == - 1) return ;
dfs2(son[u], tp);
for(int i = head[u]; i; i = e[i].next) {
int v = e[i].to;
if(v != fa[u] && v != son[u])
dfs2(v, v);
}
}
tid && Rank
tim 是访问这个节点的时间。
树链剖分是按照时间来建造整棵树的,tid[x] = tim,tid[x] 里存的是访问到 x 这个节点的时间,之后我们根据这个 tid[] 值建树,所以这个 tid[x] 就是 x 在线段树中的编号。
而 Rank[tid[x]] 就是 x 在线段树中的编号,对应在原树中的编号。
所以有:
x = Rank[tid[x]];
区间修改的思路:
x,y 之间的路径进行区间修改。
void chage(){
while(x,y不在一条重链上){
dep[top[x]] < dep[top[y]] ? swap(x,y) : 1;
对x---top[x]所在的链进行修改;
x = fa[top[x]];
}
对x---y所在的链进行修改。
}
以 x 为根节点的子树内所有节点的 tim 值一定是连续的,所以我们在对以 x 为根节点的子树内所有节点进行操作时,区间即为 (tid[x],tid[x]+size[x]−1)
通常情况下,我们会将树中的所有链剖分出来,存在线段树里进行操作,那么这样整个程序的复杂度大致 O(nlog2n)
注意赋叶子结点的值的时候和线段树不一样,应该赋的值是 a[Rank[l]]
// 记得建树!!!
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5 + 5;
struct Edge {
int to, next, w;
}e[N << 1];
int cnt = 0;
int head[N];
int n, m, r, p;
void add(int x, int y) {
e[++ cnt].to = y;
e[cnt].next = head[x];
head[x] = cnt;
}
//--------
int fa[N], dep[N], size[N], son[N];
void dfs1(int u, int f, int depth) {
fa[u] = f;
dep[u] = depth;
size[u] = 1;
for(int i = head[u]; i; i = e[i].next) {
int v = e[i].to;
if(v != fa[u]) {
dfs1(v, u, depth + 1);
size[u] += size[v];
if(son[u] == -1 || size[son[u]] < size[v])
son[u] = v;
}
}
}
int tim = 0;
int top[N], tid[N], Rank[N];
void dfs2(int u, int tp) {
top[u] = tp;
tid[u] = ++ tim;
Rank[tim] = u;
if(son[u] == - 1) return ;
dfs2(son[u], tp);
for(int i = head[u]; i; i = e[i].next) {
int v = e[i].to;
if(v != fa[u] && v != son[u])
dfs2(v, v);
}
}
//------
struct node {
ll sum, lazy;
}tree[N << 2];
ll a[N];
void push_up(int i) {
tree[i].sum = tree[i << 1].sum + tree[i << 1 | 1].sum;
}
void build(int i, int l, int r) {
tree[i].lazy = 0; // lazy 初值记得赋值为 0
if(l == r) tree[i].sum = a[Rank[l]]; // 这里赋值的时候和线段树不大一样要注意
else {
ll mid = (l + r) >> 1;
build(i << 1, l, mid);
build(i << 1 | 1, mid + 1, r);
push_up(i);
}
}
void push_down(int i, int l, int r) {
if(tree[i].lazy) {
tree[i << 1].lazy += tree[i].lazy;
tree[i << 1 | 1].lazy += tree[i].lazy;
ll mid = (l + r) >> 1;
tree[i << 1].sum += tree[i].lazy * (mid - l + 1);
tree[i << 1 | 1].sum += tree[i].lazy * (r - mid);
tree[i].lazy = 0;
}
}
void update(int i, int l, int r, int ql, int qr, int val) {
if(ql > r || qr < l) return ;
if(ql <= l && r <= qr) {
tree[i].lazy += val;
tree[i].sum += (r - l + 1) * val;
return ;
}
push_down(i, l, r);
ll mid = (l + r) >> 1;
update(i << 1, l, mid, ql, qr, val);
update(i << 1 | 1, mid + 1, r, ql, qr, val);
push_up(i);
}
ll query(int i, int l, int r, int ql, int qr) {
if(ql > r || qr < l) return 0;
if(ql <= l && r <= qr) return tree[i].sum;
push_down(i, l, r);
ll mid = (l + r) >> 1;
return (query(i << 1, l, mid, ql, qr) + query(i << 1 | 1, mid + 1, r, ql, qr)) % p;
}
//-------
void add1() {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
while(top[u] != top[v]) {
if(dep[top[u]] < dep[top[v]]) swap(u, v);
update(1, 1, n, tid[top[u]], tid[u], w);
u = fa[top[u]];
}
if(dep[u] < dep[v]) swap(u, v);
update(1, 1, n, tid[v], tid[u], w);
}
void query1() {
int u, v, ans = 0;
scanf("%d%d", &u, &v);
while(top[u] != top[v]) {
if(dep[top[u]] < dep[top[v]]) swap(u, v);
(ans += query(1, 1, n, tid[top[u]], tid[u])) %= p;
u = fa[top[u]];
}
if(dep[u] < dep[v]) swap(u, v);
(ans += query(1, 1, n, tid[v], tid[u])) %= p;
printf("%d\n", ans);
}
void add2() {
int x, w;
scanf("%d%d", &x, &w);
update(1, 1, n, tid[x], tid[x] + size[x] - 1, w);
}
void query2() {
int x;
scanf("%d", &x);
printf("%d\n", query(1, 1, n, tid[x], tid[x] + size[x] - 1) % p);
}
//-------
int main() {
memset(head, 0, sizeof(head));
memset(son, -1, sizeof(son));
scanf("%d%d%d%d", &n, &m, &r, &p);
for(int i = 1; i <= n; i ++)
scanf("%d", &a[i]);
for(int i = 1; i < n; i ++) {
int x, y;
scanf("%d%d", &x, &y);
add(x, y), add(y, x);
}
dfs1(r, 0, 1);
dfs2(r, r);
build(1, 1, n);
for(int i = 1; i <= m; i ++) {
int q;
scanf("%d", &q);
if(q == 1) add1();
else if(q == 2) query1();
else if(q == 3) add2();
else query2();
}
return 0;
}
LCA
树剖还可以用来求 LCA
int lca(int a, int b) {
while(top[a] != top[b]) {
if(dep[top[a]] > dep[top[b]]) a = fa[top[a]];
else b = fa[top[b]];
}
return (dep[a] < dep[b]) ? a : b;
}