【Educational Codeforces Round 54 (Rated for Div. 2) E. Vasya and a Tree】 dfs+树状数组

本文详细解析了E.Vasyaand Tree问题的算法实现,通过使用树状数组和DFS遍历,解决了在树形结构中进行批量节点权值更新及查询的难题。文章阐述了操作仅影响子孙节点和节点仅受祖先操作影响的性质,利用线段树原理,通过两个前缀和的差值计算节点最终权值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

E. Vasya and a Tree

题意

给你一颗n个点的树,每个点的权值最初为0给你一颗n个点的树,每个点的权值最初为0n0
有m个操作,每次操作有三个变量v,d,x有m个操作,每次操作有三个变量v,d,xmv,d,x
操作为在v的距离v&lt;=d的子树内所有节点权值+x操作为在v的距离v&lt;=d的子树内所有节点权值+xvv<=d+x
最终统计树上每个点的权值最终统计树上每个点的权值

做法

首先要明确两件事情首先要明确两件事情
性质1.每个人的操作只会影响到他的子孙(包括自己)性质1.每个人的操作只会影响到他的子孙(包括自己)1.()
性质2.每个人只会被他祖先的操作所影响(包括自己)性质2.每个人只会被他祖先的操作所影响(包括自己)2.()
也就是说,如果我们能在访问到某个节点时,统计出所有影响到该节点的祖先操作也就是说,如果我们能在访问到某个节点时,统计出所有影响到该节点的祖先操作访
就可以统计出这个节点的最终权值就可以统计出这个节点的最终权值
而对于每个操作,我们只要用一个dep数组保存每个深度被增加的值而对于每个操作,我们只要用一个dep数组保存每个深度被增加的值dep
所有深度大于当前节点的操作都会影响到当前节点,如果用线段树就是一个区间求和问题所有深度大于当前节点的操作都会影响到当前节点,如果用线段树就是一个区间求和问题线
为了减少代码量我们用树状数组,更新时只在本次操作的最深的深度更新为了减少代码量我们用树状数组,更新时只在本次操作的最深的深度更新
这样求一个1−maxdep的前缀和就是所有更新了根节点的操作这样求一个1-maxdep的前缀和就是所有更新了根节点的操作1maxdep
在求一个1−(nowdep−1)的前缀和就是所有不包含当前节点的操作在求一个1-(nowdep-1)的前缀和就是所有不包含当前节点的操作1(nowdep1)
两个前缀和相减就是当前节点被更新的值两个前缀和相减就是当前节点被更新的值
为了保证每个操作只影响自己子树内的节点,在dfs退出子树时为了保证每个操作只影响自己子树内的节点,在dfs退出子树时dfs退
要将当前根节点的所有修改值还原要将当前根节点的所有修改值还原

代码

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int maxn = 3e5+10;
int n,m;
ll tree[maxn],ans[maxn];
vector<int> G[maxn],D[maxn],X[maxn];
void add(int x,int val)
{
    while(x<=n)
    {
        tree[x]+=val;
        x=x+(x&-x);
    }
}
ll sum(int x)
{
    ll ans=0;
    while(x)
    {
        ans+=tree[x];
        x=x-(x&-x);
    }
    return ans;
}
void dfs(int x,int fa,int dep)
{
    for(int i=0;i<D[x].size();i++)
    {
        add(min(D[x][i]+dep,n),X[x][i]);//进子树之前更新
    }
    ans[x]=sum(n)-sum(dep-1);//树状数组变区间查询为两个前缀和相减
    //由于性质2,所以在这个地方就可以直接算出当前节点的最终答案
    for(int i=0;i<G[x].size();i++)
    {
        if(G[x][i]==fa) continue;
        dfs(G[x][i],x,dep+1);
    }
    for(int i=0;i<D[x].size();i++)
    {
        add(min(D[x][i]+dep,n),-X[x][i]);//出子树之后还原
    }
}
int main()
{
    int x,y,z;
    scanf("%d",&n);
    for(int i=1;i<=n-1;i++)
    {
        scanf("%d%d",&x,&y);
        G[x].push_back(y);
        G[y].push_back(x);
    }
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        D[x].push_back(y);
        X[x].push_back(z);
    }
    dfs(1,0,1);
    for(int i=1;i<=n;i++)
    {
        printf("%lld ",ans[i]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值