codeforces 383C Propagating tree

给一棵树,有20w个结点,每一个节点有一个初值,接下来有20w个询问,支持两种操作,一种是对某个节点+x,然后对子节点-x,再对孩子节点的子节点+x...,另外一种操作是查询某个节点的值。

如果用朴素的做法,查询是o(1)的,但是更新的操作是o(n)的,这样显然是不行的。

更新是对奇数层和偶数层节点进行更新,接下来神奇地运用了dfs序,对整个树进行dfs,并对点标上时间戳,更新一个节点就要对整个子树进行更新,对于一棵子树来说,节点的时间戳是连续的,对于连续的区间更新,我们就可以利用线段树了。

这里要用两棵线段树,分别维护奇数层和偶数层。更新的复杂度o(logn),查询操作o(logn),总共是o(mlogn),就可以了。

写了两道线段树的题,感觉自己写的特别丑..不过看了大神的代码也涨了很多姿势...

pq说HH的线段树不是很好...可能是因为我太渣,感觉差不多,理解起来也挺顺畅的

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <vector>
using namespace std;
const int maxn = (int)2e5+10;
#define ls rt << 1
#define rs rt << 1 | 1
#define lson  l , m , ls
#define rson  m+1 , r , rs
vector<int> G[maxn];
int val[maxn];//存点的值
int idx;
int dep[maxn];//深度
int le[maxn],ri[maxn];//时间戳


typedef struct {
    int x;
    int val;
    int num;
}seg;
seg Tree1[maxn<<3],Tree2[maxn<<3];


void build(seg T[],int l,int r,int rt){
     if(l == r){
        T[rt].x=0;
        T[rt].val=0;
        return ;
     }
     int m= (l+r) >> 1;
     build(T,lson);
     build(T,rson);
}

int query(seg T[],int p,int l,int r,int rt){//p是点在数组中的位置
     if(l==r&&l==p){
        if(T[rt].x){//如果该点需要更新
            T[rt].val+=T[rt].x;
            T[rt].x=0;
        }
        return T[rt].val;
     }
     if(T[rt].x){//如果该节点的子节点需要更新
        T[ls].x+=T[rt].x;
        T[rs].x+=T[rt].x;
        T[rt].x=0;//重置
     }
     int m = (l+r) >> 1;
     if(p<=m){
        return query(T,p,lson);
     }
     else{
        return query(T,p,rson);
     }
}

void update(seg T[],int x,int L,int R,int l,int r,int rt){
     if(L>R) return ;
     if(L<=l&&R>=r){
        T[rt].x+=x;
        return ;
     }
     if(T[rt].x){//如果该节点的子节点需要更新
        T[ls].x+=T[rt].x;
        T[rs].x+=T[rt].x;
        T[rt].x=0;
     }
     int m = (l+r) >> 1;
     if(R<=m) update(T,x,L,R,lson);
     else if(L> m) update(T,x,L,R,rson);
     else{
        update(T,x,L,R,lson);
        update(T,x,L,R,rson);
     }
}

void dfs(int x,int fa,int k){
     le[x] = ++idx;
     dep[x]=k;
     for(int i=0;i<G[x].size();i++){
        int u=G[x][i];
        if(u!=fa) dfs(u,x,k^1);//找子节点共有多少
     }
     ri[x]=idx;
}


int main()
{
    int n,m,u,v,op,Val,x,L,R,ans;
    cin>>n>>m;
    idx=0;
    for(int i=1;i<=n;i++) scanf("%d",&val[i]);
    for(int i=0;i<n-1;i++){
        scanf("%d%d",&u,&v);
        G[u].push_back(v);
        G[v].push_back(u);
    }
    dfs(1,-1,0);
    build(Tree1,1,idx,1);
    build(Tree2,1,idx,1);
    for(int j=0;j<m;j++){
        scanf("%d",&op);
        if(op==1){
            scanf("%d%d",&x,&Val);
            if(dep[x]){
                update(Tree1,Val,le[x],ri[x],1,idx,1);
                update(Tree2,-Val,le[x],ri[x],1,idx,1);
            }
            else{
                update(Tree1,-Val,le[x],ri[x],1,idx,1);
                update(Tree2,Val,le[x],ri[x],1,idx,1);
            }
        }
        else{
            scanf("%d",&x);
            if(dep[x]) ans=query(Tree1,le[x],1,idx,1)+val[x];
            else ans=query(Tree2,le[x],1,idx,1)+val[x];
            printf("%d\n",ans);
        }
    }
    return 0;
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值