>Link
luogu P3384
>Description
如题,已知一棵包含 NN 个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:
1 x y z,表示将树从 xx 到 yy 结点最短路径上所有节点的值都加上 zz。
2 x y,表示求树从 xx 到 yy 结点最短路径上所有节点的值之和。
3 x z,表示将以 xx 为根节点的子树内所有节点值都加上 zz。
4 x 表示求以 xx 为根节点的子树内所有节点值之和
>解题思路
树剖的模板题
主要是在树上进行线段树操作(树状数组也可以,但是不能懒标记,会更加费时)
(知道了原理其实还挺好打的,有点像 LCA,线段树也是模板
s
o
n
i
son_i
soni 表示第
i
i
i点的儿子中,儿子最重(子树size最大)的那一个
i
i
i 与
s
o
n
i
son_i
soni 连的边叫做 重边,多条重边连起来形成 重链
t
o
p
i
top_i
topi 表示第
i
i
i点所在的重链最上面的那个点(轻儿子则指向自己)
我们两次 d f s dfs dfs预处理,第二次 d f s dfs dfs中按照先重后轻的顺序遍历,对节点重新标号,这样就能保证一条重链上的节点标号是连在一起的,我们就可以进行线段树了!
子树的操作:重新标号,使得一棵以 x x x为根子树,编号范围为 i d x id_x idx ~ i d x + s i z x − 1 id_x+siz_x-1 idx+sizx−1
>代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 100010
#define LL long long
using namespace std;
struct edge
{
LL to, nxt;
} e[2 * N];
LL n, m, root, Mod, a[N], aa[N], hcnt, h[N];
LL cnt, t[5 * N], lazy[5 * N];
LL fa[N], dep[N], top[N], son[N], id[N], siz[N];
LL tot;
void dfs1 (LL now, LL fath)
{
fa[now] = fath;
dep[now] = dep[fath] + 1;
siz[now] = 1;
for (LL i = h[now]; i; i = e[i].nxt)
{
LL v = e[i].to;
if (v == fath) continue;
dfs1 (v, now);
siz[now] += siz[v];
if (siz[v] > siz[son[now]])
son[now] = v;
}
}
void dfs2 (LL now, LL topp)
{
id[now] = ++tot;
top[now] = topp;
if (son[now]) dfs2 (son[now], topp);
for (LL i = h[now]; i; i = e[i].nxt)
{
LL v = e[i].to;
if (v == fa[now] || v == son[now])
continue;
dfs2 (v, v);
}
}
void build_interval (LL k, LL l, LL r)
{
if (l == r)
{
t[k] = a[l];
return;
}
LL mid = (l + r) / 2;
build_interval (k * 2, l, mid);
build_interval (k * 2 + 1, mid + 1, r);
t[k] = t[k * 2] + t[k * 2 + 1];
}
void down_interval (LL k, LL l, LL r)
{
if (!lazy[k]) return;
t[k] = (t[k] + lazy[k] * (r - l + 1) % Mod) % Mod;
lazy[k * 2] = (lazy[k * 2] + lazy[k]) % Mod;
lazy[k * 2 + 1] = (lazy[k * 2 + 1] + lazy[k]) % Mod;
lazy[k] = 0;
}
void add_interval (LL k, LL l, LL r, LL ll, LL rr, LL x)
{
down_interval (k, l, r);
if (ll <= l && r <= rr)
{
lazy[k] = (lazy[k] + x) % Mod;
return;
}
LL mid = (l + r) / 2;
if (ll <= mid)
add_interval (k * 2, l, mid, ll, rr, x);
if (mid + 1 <= rr)
add_interval (k * 2 + 1, mid + 1, r, ll, rr, x);
down_interval (k * 2, l, mid);
down_interval (k * 2 + 1, mid + 1, r);
t[k] = t[k * 2] + t[k * 2 + 1];
}
void add_tree (LL x, LL y, LL z)
{
while (top[x] != top[y])
{
if (dep[top[x]] < dep[top[y]])
swap (x, y);
add_interval (1, 1, n, id[top[x]], id[x], z);
x = fa[top[x]];
}
if (id[x] > id[y]) swap (x, y);
add_interval (1, 1, n, id[x], id[y], z);
}
LL ask_interval (LL k, LL l, LL r, LL ll, LL rr)
{
down_interval (k, l, r);
if (ll <= l && r <= rr) return t[k];
LL mid = (l + r) / 2, ret = 0;
if (ll <= mid)
ret = (ret + ask_interval (k * 2, l, mid, ll, rr)) % Mod;
if (mid + 1 <= rr)
ret = (ret + ask_interval (k * 2 + 1, mid + 1, r, ll, rr)) % Mod;
return ret;
}
LL ask_tree (LL x, LL y)
{
LL ret = 0;
while (top[x] != top[y]) //不断往上跳,直到两点处于一条重链上
{
if (dep[top[x]] < dep[top[y]])
swap (x, y);
ret = (ret + ask_interval (1, 1, n, id[top[x]], id[x])) % Mod; //将每一条链进行处理
x = fa[top[x]]; //链处理过了,直接跳到链的父亲那
}
if (id[x] > id[y]) swap (x, y);
ret = (ret + ask_interval (1, 1, n, id[x], id[y])) % Mod;
return ret;
}
int main()
{
LL u, v;
scanf ("%lld%lld%lld%lld", &n, &m, &root, &Mod);
for (LL i = 1; i <= n; i++)
scanf ("%lld", &aa[i]);
for (LL i = 1; i < n; i++)
{
scanf ("%lld%lld", &u, &v);
e[++hcnt] = (edge){v, h[u]}; h[u] = hcnt;
e[++hcnt] = (edge){u, h[v]}; h[v] = hcnt;
}
dfs1 (root, 0);
dfs2 (root, root);
for (LL i = 1; i <= n; i++)
a[id[i]] = aa[i];
build_interval (1, 1, n);
LL E, x, y, z;
for (LL i = 1; i <= m; i++)
{
scanf ("%lld%lld", &E, &x);
if (E == 1)
{
scanf ("%lld%lld", &y, &z);
add_tree (x, y, z % Mod);
}
else if (E == 2)
{
scanf ("%lld", &y);
printf ("%lld\n", ask_tree (x, y));
}
else if (E == 3)
{
scanf ("%lld", &z);
add_interval (1, 1, n, id[x], id[x] + siz[x] - 1, z % Mod);
}
else
printf ("%lld\n", ask_interval (1, 1, n, id[x], id[x] + siz[x] - 1));
}
return 0;
}