【dfs序+树状数组】多次更新+求结点子树和操作,牛客小白月赛24 I题 求和

前置知识点

dfs遍历
树状数组/线段树知识

链接

I题 求和.

题意

已知有 n 个节点,有 n−1 条边,形成一个树的结构。
给定一个根节点 k,每个节点都有一个权值,节点i的权值为 vi
给 m 个操作,操作有两种类型:
1 a x :表示将节点 a 的权值加上 x
2 a :表示求 aa 节点的子树上所有节点的和(包括 a 节点本身)

坑爹的是什么?坑爹的是结点个数和操作数都是1e6级别的!

思路

第一次尝试,就是正常的dfs建树,然后dfs求出子树和,更新就老老实实向上更新,把更新求完sum的target设为1。。。

然后就老老实实地TLE了。
最坏情况可是一个1e6的树链呀!这样就O(n^2)怎么都不够了。

正确的思路:dfs序+树状数组

在这里插入图片描述
(一)dfs
首先需要dfs搞一下树。黑色序号为默认的输入顺序,红色编号为dfs的遍历顺序。我们需要使用红色的编号作为dfs序。为什么呢?我们可以看到,使用了红色编号之后,每个结点的子树编号都是连续的了
(以下编号皆为红色的dfs序)
结点1:1~6
结点2:2~4
结点3:3~3
结点4:4~4
结点5:5~5
结点6:6~6

我们在dfs遍历的同时,把每个结点的子树结点(包括本身)的起始坐标和末坐标保存在数组in[] 和数组out[]里面

当然你也可以设置一个dfn[] 序列
dfn[i]=j 表示红色编号i代表的是黑色编号j的点 【好绕啊有没有!】

见代码!

int Time=0; ///时间戳
void dfs(int x,int fa){
    in[x]=++Time;
    for(int i=0;i<P[x].size();++i){
        int v=P[x][i];
        if(v!=fa)dfs(v,x);
    }
    out[x]=Time;
}

(二)树状数组
我们搞了那么久,就是让维护的区间都是一段连续的。
比如对于结点i的子树和那就等于in[i] ~ out[i] 之间的和

那就使用线段树或者树状数组吧!这里树状数组方便一丢丢。

首先树状数组基本代码搞出来!

ll c[MAX];
ll lowbit(int x){
    return x&(-x);
}
ll query(int x){
    ll ans = 0ll;
    while(x){
        ans += c[x];
        x -= lowbit(x);
    }
    return ans;
}
void update(int x,ll v){
    while(x <= n){
        c[x] += v;
        x += lowbit(x);
    }
}

很明显,因为只有dfs序的序列才应该是树状数组所维护的c[]数组,而不是原来就输入的a[]数组。

我们每次更新,更新的下标为时间戳Time(就是dfs序的当前下标),更新的值为a[x],即当前dfs到的那个数的值。

void dfs(int x,int fa){
    in[x]=++Time;
    update(Time,a[x]);			///这里加上这句!
    for(int i=0;i<P[x].size();++i){
        int v=P[x][i];
        if(v!=fa)dfs(v,x);
    }
    out[x]=Time;
}

而对于之后的操作中的更新,我们也有办法。
对于结点x,in[x]就是该结点的dfs序,那么就简单了

if(ope==1){
    cin >> p1 >> p2;
    update(in[p1],p2);   ///下标和增加值
}

对于询问操作,因为结点x管理的子树范围是从in[x] ~ out[x],而树状数组的询问操作又是前缀和,因此我们使用一个差就可以了!注意in那里需要减掉一个1,这样才能保证差就是in[x] ~ out[x]的值。

cout << query(out[p1])-query(in[p1]-1)<< endl;

完整代码如下!

#include <bits/stdc++.h>
#define show(x) std::cerr << #x << "=" << x << std::endl
#define IOS ios_base::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
using namespace std;
/** freopen("input.in", "r", stdin);
    freopen("output.out", "w", stdout);*/
typedef long long ll;
const int MAX=1e6+50;
const int INF=1e9;
const double EPS = 0.001;
const ll MOD=1e9+7;
vector<int>P[MAX];
int in[MAX],out[MAX];
int n,Time;
int a[MAX];
ll c[MAX];
ll lowbit(int x){
    return x&(-x);
}
ll query(int x){
    ll ans = 0ll;
    while(x){
        ans += c[x];
        x -= lowbit(x);
    }
    return ans;
}
void update(int x,ll v){
    while(x <= n){
        c[x] += v;
        x += lowbit(x);
    }
}
void dfs(int x,int fa){
    in[x]=++Time;
    update(Time,a[x]);
    for(int i=0;i<P[x].size();++i){
        int v=P[x][i];
        if(v!=fa)dfs(v,x);
    }
    out[x]=Time;
}
int main()
{
    IOS;
    int m,k;
    cin >> n >> m >> k;
    for(int i=1;i<=n;++i){
        cin >> a[i];
    }
    for(int i=1;i<n;++i){
        int va,vb;
        cin >> va >> vb;
        P[va].push_back(vb);
        P[vb].push_back(va);
    }
    dfs(k,0);
    for(int i=0;i<m;++i){
        int ope,p1;
        ll p2;
        cin >> ope;
        if(ope==1){
            cin >> p1 >> p2;
            update(in[p1],p2);
        }else{
            cin >> p1;
            ///show(p1);show(in[p1]);show(out[p1]);
            cout << query(out[p1])-query(in[p1]-1)<< endl;
        }
    }
    return 0;
}
/**
5 6 1
1 2 3 4 5
1 3
1 2
2 4
2 5
1 2 10
1 3 10
1 4 5
1 5 1
2 3
2 2

====
output:
13
27
*/

这题也是经过一些dl的指点和百度、看了很多代码才明白的!
(之前还没听说过dfs序这种东东唔唔)
当时之后写的时候用线段树写一直段错误和WA,差点自闭
在这里插入图片描述
相信一切都会好起来的!

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值