• 【树上差分】CF 1076E. Vasya and a Tree


    CF 1076E. Vasya and a Tree|树上差分

    题意

    一棵树,它有n个节点,1号节点为根节点,初始所有点的权值为0。

    定义以下两个东西:

    • 函数 d ( i , j ) d(i,j) d(i,j) : 指节点 i i i j j j所经过边的数量。

    • x x x节点的 k k k级子树,指满足以下条件点的集合:

      ① x为该点的祖先,规定自己也是自己的祖先。

      d ( i , j ) ≤ k d(i,j) \leq k d(i,j)k

    m m m条要求要你来解决:

    给出 v , d , x v,d,x v,d,x,将以 v v v节点的 d d d级子树的权值加上 x x 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});
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    接下来的关键点就是如何在树上进行差分操作(对应一维差分 区间左端点的加 和 区间右端点的减)。

    因为树中的遍历是基于DFS序的,访问一个子树过后才会访问到另一棵子树

    我们在DFS过程中维护差分数组( b [ i ] b[i] b[i]),DFS时携带一个前缀和( s u m sum sum)变量,每访问到一个节点时,先遍历该节点的所有操作,将前缀和(其实就是该路径上差分数组的前缀和,等于当前节点的值)加上操作的值,然后标记差分数组结束的位置(就是在该位置减去操作的那个值)。

    当回溯的时候,删除之前打的标记。树遍历是DFS序的,删除之后才会访问到另一棵树相同的深度节点,不会影响 b [ i ] b[i] b[i]的值。

    b [ i ] b[i] b[i] : 代表深度为 i i i的节点的标记值

    标记:

    if(dep[u] + d < n - 1)
        b[dep[u] + d + 1] -= v;//mark
    
    • 1
    • 2

    删除标记:

    for(auto [d, v] : q[u])
    {
        if(dep[u] + d < n - 1)
            b[dep[u] + d + 1] += v;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    代码

    #include
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
  • 相关阅读:
    前端 TS 快速入门之六:枚举 enum
    C Primer Plus(6) 中文版 第7章 C控制语句:分支和跳转 7.5 条件运算符 ?:
    【数据结构】布隆过滤器
    python_data_analysis_and_mining_action-master-13
    Generalization
    RefConv: 重参数化的重新聚焦卷积(论文翻译)
    FastJson解析对象出现 “$ref“:“$[0].portInfoBean“ 导致list数据错乱的解决方案
    测试经理应该怎么写测试部门年终总结报告?
    SpringBoot篇之集成Mybatis-plus
    NX 1988 如何将组件转为部件
  • 原文地址:https://blog.csdn.net/qq_50285142/article/details/126186717