树状数组总结

声明:该文章同步发表于我的洛谷博客

从名字上来看,树状数组就是一个形状像树的数组。

二叉树和树状数组结构对比图:

可以发现树状数组虽然和二叉树结构相近,但是删除了一部分。

为什么呢?

再给出另一张结构图:

可以发现,每一层中的第偶数个格子是无用的,第奇数个格子是有用的,

这是因为,对于第 i 层第 j 个格子(j \bmod 2=0),一定可以用第 i-1 层第 j-1 个格子减去第 i 层第 j-1 个格子得到。

那么怎么求解呢?

我们用 c_i 维护一个树状数组。

初始化:

c_1=a_1\\ c_2=a_1+a_2\\ c_3=a_3\\ c_4=a_1+a_2+a_3+a_4\\ \cdots

是不是挺麻烦的?

没错!我们可以用前缀和计算,记为 sum

得出结论:c_i=sum_i-sum_{i-f(i)}f(i)=i \land (-i))。

Q:函数 f 是干什么的?

A:对于 if(i) 的返回值为 i 的二进制抹掉最后一位 0

用 C++ 代码表示为:

int lowbit(int x){
    return x&(-x);
}

Q:ST 表的区间长度为 2^j 次方,那么树状数组的区间长度为多少呢?

A:根据上面的式子,可以得出 c_i 表示的区间长度为 f(i)

单点修改,区间查询

说了这么多,还没讲树状数组怎么用。

如果我们修改了第 i 个点,那么只需要更新 i 以及它的父节点,即:

void modify(int x,int val){
    while(x<=n){
        c[x]+=val;
        x+=lowbit(x);
    }
}

在查询时,我们将 i 和它的子节点的值加起来,实现为:

void modify(int x,int val){
    while(x<=n){
        c[x]+=val;
        x+=lowbit(x);
    }
}


那怎么区间查询呢?如果我们维护的是区间 [l,r] 元素之和,那么可以用类似于前缀和查询的操作,将区间 [1,r] 的结果减去区间 [1,l-1] 的结果。

区间修改,单点查询

对于这种情况,单点修改已经不能满足我们的要求(总不可能一个一个去加吧),既然是单点查询,那么我们可以考虑使用差分的思想。

假设我们要将区间 [l,r] 的每一个数加上 x,那么我们将 l 加上 xr+1 减去 x,注意这种情况 c 不需要初始化,因为 c 维护的是差分的值。

查询时,输出 a[i]+query(i)。

区间修改,区间查询

好像大家都说这玩意用线段树才能做?

其实某些特殊的地方还是可以用的。

放道例题:P2184 贪婪大陆

一张示意图:

假设我们已经求出了区间 [l1,r1][l2,r2] 的结果,现在我们添加一个区间 [l3,r3]

不难发现,区间 [l3,r3] 内树的种类等于 [1,r3] 中左边界的数量减去区间 [1,l-1] 中右边界的数量。

所以我们就将这题转化为一道树状数组单点修改,区间查询的题目了。

注意:树状数组对于区间修改,区间查询问题仅能解决一部分特殊的问题。所以大家还是老老实实地去学习线段树吧。

离散化

本来不想写的,可以某些地方要用啊。

假设某道题,元素的最大数值达到了 10^9,但是元素数量只有 10^5,这时候你会选择怎么办呢?

我们通常使用的方法为离散化

方法1

首先将原数组复制一份。

然后将副本排序,将副本中的数据去重(否则可能会出现数字相同但是离散化后的值不同的情况)。

将原数组的每一个元素在副本中的位置作为离散化后的值。

方法2

使用数据结构(用 C++ 自带的 map/unordered_map)。

对于每一个数据,如果之前没出现过,那么给它分配一个编号并返回,否则返回之前分配的编号。

Q:是使用 map 更优还是用 unordered_map 更优呢?

A:根据情况而定。一般情况下 unordered_map 会比 map 要更快,但是不太稳定,容易被卡。建议在 OI 赛制中或打 Codeforces 时使用 map,因为在 OI 赛制中看不到实时反馈,Codeforces 比赛中用 unordered_map 基本会被其他选手 Hack 掉。如果在其他赛制中用 map 只超时了一点点,可以考虑用 unordered_map 卡常。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值