Codeforces 383C Propagating tree 题解&代码

题意:
给出一个以1为根节点的n个节点的树,有两个操作:
1、操作为1 x val,给x节点增加val
2、操作为2 x,查询x节点当前的值
每次给一个节点的值增加val,这个节点的孩子的值将会增加-val
如此,给一个节点增加val就相当于它的子节点增加-val,它的子节点的子节点增加val…如此直到叶子节点

思路:
每次都是给修改节点子树中与修改节点深度奇偶性相同的节点增加val,给与修改节点深度奇偶性不同的节点增加-val
我们可以建两个范围均为[1,n]的线段树,线段树的根节点编号为0和1,表示他们存储了深度(深度大小在一遍dfs中很容易O(n)地记录下来)分别为奇数和偶数的节点的状态。如果某个线段树存储了深度为奇数的节点状态,那么如果这个线段树在建树时遇到了深度为偶数的节点,就作一个不可能标记(不可能由数据范围得到的值),否则插入现在应当插入线段树的点。反之则反。
插入操作是同时对两个树进行的,在与待插入节点x奇偶性相同的树中,给x和它的子树中所有节点都增加val;在与待插入节点x奇偶性相反的树中,给x和它的子树中所有节点都增加-val。由于两个树中的存储情况正好互补(即如果一个点在线段树0中无法查询,那它一定可以在线段树1中被查找到),所以其子树中每一个存在的节点都唯一地被改变了val值(存在不可能标记的时候对这个节点不做修改)。区间修改和编号重排用普通的dfs序+lazy[]标记即可。
单点查询操作也是两个树同时进行,由于两个树的互补性,一定有一个树可以得到正确答案,另一个树中则无法查询该节点(节点上有不可能标记),查询到结果的树返回结果,无法查询的树只要返回0(不改变结果)即可,加起来一定是正确答案。

#include<iostream>
#include<vector>
#include<stdio.h>
#define lson (o<<1)
#define rson ((o<<1)|1)
#define INF 2100000000
using namespace std;
const int maxn=200005;
vector<int> edge[maxn];
int n,m,c,x,y,cnt,val[maxn],value[2][maxn],tot[2],v[2][maxn],deep[maxn],in[maxn],out[maxn],lazy[2][maxn*4];
void dfs(int x,int pre)
{
    deep[x]=deep[pre]+1;
    in[x]=++cnt;
    v[deep[x]%2][in[x]]=x;
    for(int i=0;i<edge[x].size();i++)
        if(edge[x][i]!=pre)dfs(edge[x][i],x);
    out[x]=cnt;
}
void build(int z,int o,int l,int r)
{
    if(l==r)
    {
        if(v[z][l])value[z][l]=val[v[z][l]];
        else value[z][l]=INF;
        return;
    }
    int mid=(l+r)/2;
    build(z,lson,l,mid);
    build(z,rson,mid+1,r);
}
void pushdown(int z,int o,int l,int r)
{
    if(!lazy[z][o])return;
    if(l==r)
    {
        if(value[z][l]!=INF)
            value[z][l]+=lazy[z][o];
    }
    else
    {
        lazy[z][lson]+=lazy[z][o];
        lazy[z][rson]+=lazy[z][o];
    }
    lazy[z][o]=0;
}
void addtree(int z,int o,int l,int r,int L,int R,int c)
{
    pushdown(z,o,l,r);
    if(l>R || r<L)return;
    if(l>=L && r<=R)
    {
        lazy[z][o]+=c;
        return;
    }
    int mid=(l+r)/2;
    addtree(z,lson,l,mid,L,R,c);
    addtree(z,rson,mid+1,r,L,R,c);
}
int query(int z,int o,int l,int r,int L,int R)
{
    pushdown(z,o,l,r);
    if(l>R || r<L)return 0;
    if(l==r)
    {
        if(value[z][l]==INF)return 0;
        return value[z][l];
    }
    int mid=(l+r)/2;
    return query(z,lson,l,mid,L,R)+query(z,rson,mid+1,r,L,R);
}
int main(void)
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&val[i]);
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&x,&y);
        edge[x].push_back(y);
        edge[y].push_back(x);
    }
    dfs(1,0);
    build(0,1,1,n);
    build(1,1,1,n);
    for(int i=1;i<=m;i++)
    {
        scanf("%d",&c);
        if(c==1)
        {
            scanf("%d%d",&x,&y);
            addtree(deep[x]%2,1,1,n,in[x],out[x],y);
            addtree(!(deep[x]%2),1,1,n,in[x],out[x],-y);
        }
        else
        {
            scanf("%d",&x);
            printf("%d\n",query(deep[x]%2,1,1,n,in[x],in[x])+query(!(deep[x]%2),1,1,n,in[x],in[x]));
        }
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值