题目大意
有一棵点数为 N 的树,以点 1 为根,且树点有边权。然后有 M 个操作,分为三种:
操作 1 :把某个节点 x 的点权增加 a 。
操作 2 :把某个节点 x 为根的子树中所有点的点权都增加 a 。
操作 3 :询问某个节点 x 到根的路径中所有点的点权和。
原题链接
做法
- 链式前向星加边
- 两次dfs,第一次算出父节点,子树大小,深度,重儿子,第二次算出每个点的dfs序号,每一条链的链首
- 建线段树
- 树链剖分
代码
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
const int INF = 0x3f3f3f3f;
int n, m, tot = 0, cnt = 0;
int head[N];
//父节点,子树大小,重儿子,深度
int anc[N], size[N], son[N], dep[N];
//链首, dfs的时间戳以及它的反函数
int top[N], dfn[N], pre[N];
//点权
ll w[N];
struct edge{
int to, nxt;
}e[N << 1];
inline void add(int u, int v){
e[++tot] = {v, head[u]};
head[u] = tot;
}
void dfs1(int u, int fa){
anc[u] = fa;
size[u] = 1;
dep[u] = dep[fa] + 1;
for (int i = head[u]; i; i = e[i].nxt) {
int v = e[i].to;
if (v == fa) continue;
dfs1(v, u);
size[u] += size[v];
if (size[v] > size[son[u]])
son[u] = v;
}
}
void dfs2(int u, int tp){
top[u] = tp;
dfn[u] = ++cnt;
pre[cnt] = u;
if (son[u])
dfs2(son[u], tp);
for (int i = head[u]; i; i = e[i].nxt) {
int v = e[i].to;
if (v != son[u] && v != anc[u])
dfs2(v, v);
}
}
//线段树
//支持单点更新,区间更新,区间查询
struct tree{
int l, r;
ll sum, la;
int mid(){
return l + r >> 1;
}
}t[N << 2];
inline void push_up(int i){
t[i].sum = t[i << 1].sum + t[i << 1|1].sum;
}
inline void buildTree(int i, int l, int r){
t[i].l = l, t[i].r = r;
if (l == r){
t[i].sum = w[pre[l]];
return;
}
int mid = t[i].mid();
buildTree(i << 1, l, mid);
buildTree(i << 1|1, mid + 1, r);
push_up(i);
}
inline void push_down(int i){
t[i << 1].la += t[i].la;
t[i << 1|1].la += t[i].la;
t[i << 1].sum += t[i].la * (t[i << 1].r - t[i << 1].l + 1);
t[i << 1|1].sum += t[i].la * (t[i << 1|1].r - t[i << 1|1].l + 1);
t[i].la = 0;
}
inline void add_point(int i, int x, ll val){
if (t[i].l == t[i].r){
t[i].sum += val;
t[i].la += val;
return;
}
if (t[i].la);
push_down(i);
int mid = t[i].mid();
if (x <= mid)
add_point(i << 1, x, val);
else
add_point(i << 1|1, x, val);
push_up(i);
}
inline void add_interval(int i, int l, int r, ll val){
if (t[i].l >= l && t[i].r <= r){
t[i].la += val;
t[i].sum += val * (t[i].r - t[i].l + 1);
return;
}
if (t[i].la)
push_down(i);
int mid = t[i].mid();
if (l <= mid)
add_interval(i << 1, l, r, val);
if (r > mid)
add_interval(i << 1|1, l, r, val);
push_up(i);
}
ll query_interval(int i, int l, int r){
if (t[i].l >= l && t[i].r <= r){
return t[i].sum ;
}
push_down(i);
ll res = 0;
int mid = t[i].mid();
if (l <= mid)
res += query_interval(i << 1, l, r);
if (r > mid)
res += query_interval(i << 1|1, l, r);
return res;
}
//子树点权和的询问
ll tree_sum(int u, int v){
ll ans = 0;
//不在同一条链上不断向上找
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]]) {
swap(u, v);
}
ans += query_interval(1, dfn[top[u]], dfn[u]);
u = anc[top[u]];
}
if(dfn[u] > dfn[v])
swap(u, v);
ans += query_interval(1,dfn[u], dfn[v]);
return ans;
}
inline void solve(){
int op = 0, x = 0;
ll y = 0;
cin >> op >> x;
if (op == 1){
cin >> y;
add_point(1, dfn[x], y);
}else if (op == 2){
cin >> y;
add_interval(1, dfn[x], dfn[x] + size[x] - 1, y);
}else if(op == 3){
cout << tree_sum(1, x) << endl;
}
}
int main() {
//freopen("in.txt", "r", stdin);
ios::sync_with_stdio(false);
cin >> n >> m;
for (int i = 1; i <= n; ++i)
cin >> w[i];
for (int i = 1, x, y; i < n; ++i) {
cin >> x >> y;
add(x, y), add(y, x);
}
dfs1(1, -1);
dfs2(1, 1);
buildTree(1, 1, n);
while (m--)
solve();
return 0;
}