前言

大佬们都说是经典题型,但对我这种小白来说,这题确实挺难挺不错的,思考了好久才差不多弄明白。
下面我会详细的讲解一下。

题目链接

https://ac.nowcoder.com/acm/contest/5158/I

题目大意

一棵树,n个节点,m次操作,k为根节点;
输入n个正整数,表示第i个数的权值;
输入n-1对数,为节点,表示两者之间有边;
输入m个组数,每组代表一次操作:
1 node add :表示将节点 node 的权值加上 add //加数
2 node :表示求 node 节点的子树上所有节点的和(包括 node 节点本身) //输出

解题思路

我没想出是这个方法。
我的错误思路:
我当时的错误方法是记忆化搜索,用father[i]表示第i个节点的父亲,再用sum数组去存储子树和。通过每次更新数值都要记忆化搜索一次,更新路径上的sum值。输出sum值。
但是相当于每次更新都要dfs一次,本身dfs一次的递归次数就很多,还要每次更新都dfs一次,必然TLE。
因此,这方法不可行。
大佬的正确思路:
dfs序+树状数组
不知道大家是否听说过这两个东西,我没听说过dfs序,听说过树状数组,仅限听说,听雨巨说。
给大家先讲讲dfs序和树状数组(了解的可以忽略)
dfs序(基础讲解)
树状数组
要说为什么能想到,我感觉如果我知道dfs序与树状数组的搭配用法,我应该也能想到这种方法,毕竟挺经典的而且挺基础的,但是能不能成功实现另说了QWQ

AC代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e6+10;
int n,m,k;
int Time=0;
int a[N];
ll c[N];
int in[N],out[N];
vector<int> node[N];
//树状数组板子函数
int lowbit(int x){
    return x&(-x);
}
void update(int x,int add){
    while(x<=n){
        c[x]+=add;
        x+=lowbit(x);
    }    
}
ll getsum(int x){
    ll ans=0LL;
    while(x>0){
        ans+=c[x];
        x-=lowbit(x);
    }
    return ans;
}
//几乎是dfs序板子函数,一点变化下面讲解
void dfs(int x,int fa){
    in[x]=++Time;
    update(Time,a[x]);
    for(int i=0;i<node[x].size();i++){
        if(fa!=node[x][i]) dfs(node[x][i],x);
    }
    out[x]=Time;
}
int main(){
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
        //简单的输入存储
    for(int i=1;i<n;i++){
        int  u,v;
        cin>>u>>v;
        node[u].push_back(v);
        node[v].push_back(u);
    }
    //简单的存边

    dfs(k,0);
    //
    for(int i=1;i<=m;i++){
        int op,p,add;
        cin>>op;
        if(op==1){//add
            cin>>p>>add;
            update(in[p],add);
        }
        else{//print
            cin>>p;
            cout<<getsum(out[p])-getsum(in[p]-1)<<endl;
        }
    }
} 

代码讲解

注意1:树状数组的函数中操作的下标不再是节点序序号(链接中树状数组代码讲解中是节点序序号),而是dfs序序号。
因此,update的作用是将dfs序序号为x的节点对应的树状数组c中的值加上add;getsum的作用是求前缀和,根据dfs序排序的前缀和,并非子树和,如图
图片说明

上面一行是dfs序序号,下面是对应的节点序序号
若getsum(4),则得到的结果为5+8+2+1=16;getsum(1),得到的结果为5。(getsum函数的内部实现是通过累加c数组的值算得的)
注意2:update中不要更新a数组的值,因为在dfs函数中update的参数为a[x],若a数组值发生改变,会影响c数组的赋值。
注意3:dfs中的update函数的目的是将包含dfs序序号为Time(其实Time本身就是在记录dfs序序号)节点的所有节点的c数组的值加上a[节点序序号],因为此时c数组全零,此时相当于初始化。
注意4:op==1后执行的update函数的参数为in[p],因为p为节点序序号,in[p]为p对应的dfs序序号,一切操作基于dfs序!
注意5:输出的时候,前缀和思想,in[p]需减一,求出dfs序序号从in[p]~out[p]的和,即以节点序序号为p的点为根节点子树的权值和。

总结

涨知识,第一次做树状数组的题就这么难,不过这样也算是彻底把树状数组的入门知识学明白了,花了一下午写的题解根本不亏!
实现起来还是挺难的!
现在重点是理解dfs序和树状数组的过程实现,从而更好的记住函数代码。
(其实这个题,我拉了4天的战线才学完的……引以为戒吧,借口:期末!理由充分!)
最后的最后,我还想问上天一句:我什么时候才能成为大佬啊啊啊!!!