题意:
给定一棵结点数为 n 的有根树,1 号结点为根,深度为 0,每个结点初始权值为 0。现有 q 个操作,①:1 L X,将深度为 L 的结点权值加上 X;②:2 X,询问以 X 为根的子树权值和。(n, q <= 1e5)
链接:
https://nanti.jisuanke.com/t/A1998
解题思路:
子树查询,转化为dfs序区间查询,主要在于修改操作。考虑分块,对于结点数不超过 n \sqrt{n} n 的层,直接暴力修改;否则,打上标记,询问时对标记层二分落在dfs序区间的结点区间。由于结点数大于 n \sqrt{n} n 的层不超过 n \sqrt{n} n,修改和查询复杂度都是O( n \sqrt{n} nlogn)。
参考代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define pb push_back
#define sz(a) ((int)a.size())
#define mem(a, b) memset(a, b, sizeof a)
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
const int maxn = 1e5 + 5;
const int maxm = 5e2 + 5;
const int mod = 998244353;
const int inf = 0x3f3f3f3f;
vector<int> G[maxn], P[maxn], B;
int in[maxn], out[maxn], rk[maxn], dep[maxn];
ll c[maxn], add[maxn];
int n, q, tim;
void dfs(int u){
in[u] = ++tim, rk[tim] = u;
for(int i = 0; i < sz(G[u]); ++i){
int v = G[u][i];
dep[v] = dep[u] + 1;
dfs(v);
}
out[u] = tim;
}
#define lowb(x) ((x)&(-x))
void update(int x, int v){
while(x <= n) c[x] += v, x += lowb(x);
}
ll query(int x){
ll ret = 0;
while(x) ret += c[x], x -= lowb(x);
return ret;
}
int main(){
scanf("%d%d", &n, &q);
for(int i = 1; i < n; ++i){
int u, v; scanf("%d%d", &u, &v);
G[u].pb(v);
}
dfs(1);
for(int i = 1; i <= n; ++i){
P[dep[rk[i]]].pb(i);
}
int len = sqrt(n);
for(int i = 0; i < n; ++i){
if(sz(P[i]) > len) B.pb(i);
}
while(q--){
int opt, x, y; scanf("%d%d", &opt, &x);
if(opt == 1){
scanf("%d", &y);
if(sz(P[x]) <= len){
for(int i = 0; i < sz(P[x]); ++i) update(P[x][i], y);
}
else add[x] += y;
}
else{
ll ret = query(out[x]) - query(in[x] - 1);
for(int i = 0; i < sz(B); ++i){
if(!add[B[i]]) continue;
int l = lower_bound(P[B[i]].begin(), P[B[i]].end(), in[x]) - P[B[i]].begin();
int r = lower_bound(P[B[i]].begin(), P[B[i]].end(), out[x] + 1) - P[B[i]].begin();
if(l >= r) continue;
ret += add[B[i]] * (r - l);
}
printf("%lld\n", ret);
}
}
return 0;
}