CF 1076E. Vasya and a Tree|树上差分
题意
一棵树,它有n个节点,1号节点为根节点,初始所有点的权值为0。
定义以下两个东西:
函数 : 指节点到所经过边的数量。
节点的级子树,指满足以下条件点的集合:
① x为该点的祖先,规定自己也是自己的祖先。
②。
条要求要你来解决:
给出,将以节点的级子树的权值加上。
当处理完所有的要求时,输出所有点的权值。
思路
题目要求对一段深度的某节点的子树进行加和操作,这种操作类似单纯的一维前缀和差分操作,所以可以在树上进行差分操作。
首先就是将所有的操作离线下来,将所有操作挂在节点上。对应下面的代码
vector<vector<pair<int, int>>> q(n + 1);
for(int i = 1; i <= m; i++)
{
int u, d, v;
cin >> u >> d >> v;
q[u].push_back({d, v});
}
接下来的关键点就是如何在树上进行差分操作(对应一维差分 区间左端点的加 和 区间右端点的减)。
因为树中的遍历是基于DFS序的,访问一个子树过后才会访问到另一棵子树。
我们在DFS过程中维护差分数组(),DFS时携带一个前缀和()变量,每访问到一个节点时,先遍历该节点的所有操作,将前缀和(其实就是该路径上差分数组的前缀和,等于当前节点的值)加上操作的值,然后标记差分数组结束的位置(就是在该位置减去操作的那个值)。
当回溯的时候,删除之前打的标记。树遍历是DFS序的,删除之后才会访问到另一棵树相同的深度节点,不会影响的值。
: 代表深度为的节点的标记值
标记:
if(dep[u] + d < n - 1)
b[dep[u] + d + 1] -= v;//mark
删除标记:
for(auto [d, v] : q[u])
{
if(dep[u] + d < n - 1)
b[dep[u] + d + 1] += v;
}
代码
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
vector<vector<int>> g(n + 1);
vector<int> dep(n + 1);
for(int i = 1; i < n; i++)
{
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
int m;
cin >> m;
vector<vector<pair<int, int>>> q(n + 1);
for(int i = 1; i <= m; i++)
{
int u, d, v;
cin >> u >> d >> v;
q[u].push_back({d, v});
}
vector<ll> b(3e5 + 1);
vector<ll> ans(n + 1);
function<void(int, int, ll)> dfs = [&](int u, int fa, ll sum)
{
// 加上标记值
sum += b[dep[u]]; // minus
// 枚举节点操作
for(auto [d, v] : q[u])
{
sum += v;//累加
if(dep[u] + d < n - 1)
b[dep[u] + d + 1] -= v;//mark
}
ans[u] = sum;
for(auto v : g[u])
{
if(v == fa) continue;
dep[v] = dep[u] + 1;
dfs(v, u, sum);
}
// 删除标记
for(auto [d, v] : q[u])
{
if(dep[u] + d < n - 1)
b[dep[u] + d + 1] += v;
}
};
dfs(1, -1, 0);
for(int i = 1; i <= n; i++)
cout << ans[i] << " \n"[i == n];
return 0;
}