CF276E Little Girl and Problem on Trees(线段树+树状数组)
Little Girl and Problem on Trees
题意:
给定一棵无边权的树,除了根节点以外的节点度数不超过 \(2\),有两种操作
- (
0 v x d)将距离 \(u\) 节点 \(d\) 距离之内的节点的值加上 \(x\) - (
1 v)询问 \(u\) 节点的值
\(n\le 100000\),\(q\le 100000\)
思路:
通过两个操作的描述,可以知道要实现的是区间修改和单点查询两个操作,不难想到要用线段树来实现.
//线段树
struct Node {
int tag, len;
i64 sum;
} tr[N << 2];
void pull(int u) {
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void build(int u, int l, int r) {
tr[u].len = r - l + 1;
if (l == r) return ;
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
}
void settag(int u, int x) {
tr[u].sum += x * tr[u].len;
tr[u].tag += x;
}
void push(int u) {
if (!tr[u].tag) return ;
settag(u << 1, tr[u].tag);
settag(u << 1 | 1, tr[u].tag);
tr[u].tag = 0;
}
void modify(int u, int l, int r, int ln, int rn, int x) {
if (l >= ln && r <= rn) return void(settag(u, x));
int mid = (l + r) >> 1;
push(u);
if (mid >= ln) modify(u << 1, l, mid, ln, rn, x);
if (mid < rn) modify(u << 1 | 1, mid + 1, r, ln, rn, x);
pull(u);
}
int query(int u, int l, int r, int pos) {
if (l == r) return tr[u].sum;
int mid = (l + r) >> 1;
push(u);
if (mid >= pos) return query(u << 1, l, mid, pos);
else return query(u << 1 | 1, mid + 1, r, pos);
}
但是这是在树上,所以很自然的就想到将整棵树剖成一个序列,用 \(dfs\) 序来表示这个树。
struct node {
int v, nxt;
}e[N * 2];
void connect(int a, int b) {
e[++idx] = {b, h[a]}, h[a] = idx;
}
void dfs(int u, int fa) {
dep[u] = dep[fa] + 1;
top[u] = u;
dfn[u] = ++cur;
for (int i = h[u]; i; i = e[i].nxt) {
if (e[i].v == fa) continue;
dfs(e[i].v, u);
if (dep[top[e[i].v]] > dep[top[u]])
top[u] = top[e[i].v];
}
}
接下来要考虑操作一中距离节点 \(u\) 距离为 \(d\) 的所有节点都加上 \(x\) ,这个包括的范围是在 \(dfn[u] \pm d\) 所以修改的区间范围就出来了。但是不能够直接对 \([dfn_u - d, dfn_u + d]\) 这个区间进行区间修改。由于题目中说的除了根节点之外的所有的点度数不超过 \(2\) 所以可以知道所有的点都是在各自的链上,树大致长这个样, 其中 \(1\) 是根节点。

那么对于距离 \(d\) 就会出现 \(dfn_u + d\) 超过了这个链的长度,此时就要将右边界限制在这条链最深的节点的深度。再考虑左边界,如果 \(dep_u - d > 1\) 那么左边界就还是在这一条链上;但如果 \(dep_u - d < 0\) 就会出现离节点 \(u\) 距离为 \(d\) 的点出现在另外一条链上,这样的话线段树就很难操作了,因为其他所有的链都是要加上 \(x\) 的。那么可以考虑将需要加在所有链上的贡献归结到根节点上,将距离根节点 \(d - dep_u\) 的所有点都加上 \(x\) ,这个操作可以等同于单点加法,将所有点的深度作为区间,维护前缀和,基于这一点可以用树状数组来维护.
void add(int x, int v) {
for (int i = x; i <= n; i += i & -i) c[i] += v;
}
i64 ask(int x) {
i64 ans = 0;
for (int i = x; i; i -= i & -i) ans += c[i];
return ans;
}
i64 range(int l, int r) {
return ask(r) - ask(l);
}
每一次查询的结果就是 \(tr_n - tr_{dep_u - 1}\)。
signed main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
std::cin >> n >> q;
for (int i = 1; i < n; i++) {
int u, v;
std::cin >> u >> v;
connect(u, v), connect(v, u);
}
dfs(1, 0);
build(1, 1, n);
for (int i = 0; i < q; i++) {
int op, v;
std::cin >> op >> v;
if (op == 0) {
int x, d; std::cin >> x >> d;
if (v == 1) {
add(d + 1, x);
continue;
}
int l = 0, r = dfn[v] + std::min(d, dep[top[v]] - dep[v]);
if (dep[v] - 1 > d) {
l = dfn[v] - d;
} else {
l = dfn[v] - (dep[v] - 2);
int p = d - dep[v] + 1;
modify(1, 1, n, l, std::min(p + l - 1, dfn[top[v]]), -x);
add(p + 1, x);
}
modify(1, 1, n, l, r, x);
} else {
i64 ans = query(1, 1, n, dfn[v]);
ans += range(dep[v] - 1, n);
std::cout << ans << "\n";
}
}
return 0 ^ 0;
}

浙公网安备 33010602011771号