我们今天来讲一个应用比较广泛的数据结构——树状数组

它可以在O(nlogn)的复杂度下进行单点修改区间查询,下面我会分成三个模块对树状数组进行详细的解说,分别是树状数组基本操作、树状数组区间修改单点查询的实现、树状数组查询最值的实现


一.

树状数组一般分为三种操作,初始化、修改、查询

在讲基本操作之前,我们先来看一张图

这张图就是树状数组的存储方式,对于没有接触过树状数组的人来说看懂上面这张图可能有些困难,上图的A数组就是我们的原数组,C数组则是我们需要维护的数组,这样存储能干什么呢,比如我们在查询A[1~7]数组的前缀和时,可以将C[4]、C[6]、C[7]三个数组的值加起来,那么它就是我们要求的A[1~7]的前缀和,那么为什么是C[4]、C[6]、C[7]三个数组呢

我们来看一下4、6、7在二进制下是什么

7 = 2+ 21 + 2 = (111)2

6 = 22 + 2 = (110)2

4 = 22 = (100)2

于是我们发现,从7到4,我们的二进制的1从最后一位开始减少,再根据上图,我们可以发现,C[i]数组存储区间为[ i - 2k + 1,i],其中k是二进制下i末尾的0的个数

这里我们用lowbit(x)表示x在二进制下C[ i ]的存储长度,lowbit的求法如下:

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

关于它的原理,如下:

二进制下的负数是将原来的整数取反后加1表示的,就像这样:

原数字 x:(100101000)2

取反 ~x:(011010111)2

负数 ~x+1:(011011000)2

我们发现,这样操作后,末尾1前面的数都被取反了,而后面的数全部变成了0,于是

x & (-x) = (000001000)2

这样,我们就将C[ i ]每次的存储长度求出来了

下面我们讲树状数组的基本操作

inline void add(int x,int y) //给x加上y
{while(x <= n){c[x] += y;x += lowbit(x);}return ;
}inline int query(int x) //查询1~7的前缀和
{int ans = 0;while(x){ans += c[x];x -= lowbit(x);}return ans;
}

当我们求区间l ~ r的区间和时,可以用C[r] - C[l - 1]

如果你还是不太懂,就结合着上面的图和代码,感性理解一下


二.

下面我们讲树状数组如何做到区间修改(单点查询过于简单我就不提了)

我们可以发现,当我们让区间l ~ r加上v时,实际上相当于我们先将区间l ~ n加上v,再将区间r + 1 ~ n减去v

所以,第一种方法,我们可以再维护一个树状数组B用来存区间修改的情况,当我们查询某一点i时,用B[1~i]的前缀和加上我们原来维护的数组,就可以做到区间修改单点查询了

不过,在这里我们有一种巧妙的方法可以不用维护两个树状数组,我们可以维护一个差分数组C,让A[i] - A[i - 1]加入数组C,这样我们在修改区间l ~ r时,只要让l加上v,再让r + 1减去v就行了,证明非常简单,因为l ~ r中的数同时加上了一个数,A[i] - A[i - 1]不变

这样我们就用一个树状数组进行了区间修改单点查询操作,在查询一个数x时,A[x]就是C[1~x]的前缀和

有一道洛谷的板子题可供练习

洛谷 P3368

代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>const int maxn = 1e6 + 6;
int n,m;
int cost[maxn];inline int read()
{char ch = getchar();int x = 0,f = 1;while(ch<'0'||ch>'9'){if(ch == '-') f = -1; ch = getchar();}while(ch>='0'&&ch<='9'){x = x*10 + ch -'0'; ch = getchar();}return x*f;
}inline int lb(int  x)
{return x&(-x);
}inline void add(int a,int v)
{while(a<=n){cost[a] += v;a += lb(a);}
}inline int query(int a)
{int ans = 0;while(a){ans += cost[a];a -= lb(a);}return ans;
}int main(int argc, char const *argv[])
{n = read();m = read();int pre = 0;for(int i = 1;i <= n;i ++){int val = read();add(i,val - pre);pre = val;}for(int i = 1;i <= m;i ++){int flag,x,y,k;flag = read();if(flag == 1){x = read();y = read();k = read();add(x,k);add(y+1,-k);}else{x = read();printf("%d\n",query(x));}}return 0;
}


三.

下面我们来讲树状数组查询区间最值,虽然它的复杂度为O(nlognlogn),但是依然是我们可以接受的

我们来考虑如何维护最值,一个显然的方法我们对于每个元素都让它自身的值和覆盖它的C数组取最值,复杂度为O(nlogn),然后当我们要改变一个数的值的时候,将C数组清空然后重新维护,显然这个复杂度并不理想

我们接着考虑有没有更快的维护方法,于是我们发现,对于一个区间C[x],能转移到它的只有C[x - 20]、C[x - 21]、C[x - 22]...C[x - 2 k],且2< lowbit(x),2k+1 >= lowbit(x)

这样,我们就维护出区间C[x]的最值了,且维护的复杂度为O(lognlogn)

代码如下:

inline void modify(int x,int y) //把x的值改为y
{a[x] = y;while(x <= n){c[x] = a[x];for(int i = 1;i < lowbit(x);i <<= 1) c[x] = std::max(c[x],c[x - i]);x += lowbit(x);}return ;
}

查询的方法也非常简单,我们从查询的右端点开始找被包含的C数组,若r - lowbit(r) >= l,就用C[r]维护最值,否则,我们就直接用A[r]维护最值

代码如下:

inline int query(int l,int y) //查询区间l ~ r的最大值
{int ans = 0;while(l <= r){ans = std::max(ans,a[r]);r--;while(l <= r - lowbit(r)){ans = std::max(ans,c[r]);r -= lowbit(r);}}return ans;
}

这里有一道例题

HDU 1754

就是一个树状数组维护最值的板子题,代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>const int maxn = 2e5+5;
int n,m;
int a[maxn];
int cost[maxn];inline int lb(int x)
{return x&(-x);
}inline void modify(int x)
{while(x<=n){cost[x] = a[x];for(int i = 1;i < lb(x);i<<=1) cost[x] = std::max(cost[x],cost[x-i]);x += lb(x);}
}inline int query(int l,int r)
{int ans = 0;while(l <= r){ans = std::max(ans,a[r]);r --;while(l <= r-lb(r)) {ans = std::max(ans,cost[r]);r -= lb(r);}}return ans;
}int main()
{while(scanf("%d %d",&n,&m)!=EOF){memset(a,0,sizeof(a));memset(cost,0,sizeof(cost));for(int i = 1;i <= n;i ++){scanf("%d",&a[i]);modify(i);}for(int i = 1;i <= m;i ++){char flag = getchar();while(flag!='Q'&&flag!='U')flag = getchar();if(flag=='Q'){int l,r;scanf("%d %d",&l,&r);printf("%d\n",query(l,r));}else if(flag=='U'){int x,v;scanf("%d %d",&x,&v);a[x] = v;modify(x);}}}return 0;
} 

转载于:https://www.cnblogs.com/Ackers/p/10090713.html

数据结构——树状数组相关推荐

  1. js 数组 实现 完全树_算法和数据结构 | 树状数组(Binary Indexed Tree)

    本文来源于力扣圈子,作者:胡小旭.点击查看原文 力扣​leetcode-cn.com 树状数组或二叉索引树(英语:Binary Indexed Tree),又以其发明者命名为 Fenwick 树.其初 ...

  2. 数据结构--树状数组

    文章目录 1. 树状数组 2. 单点修改 3. 区间修改 4. 完整代码 5. 参考文献 1. 树状数组 类似数据结构:线段树(Segment Tree) 树状数组 跟 线段树 的区别: 树状数组能做 ...

  3. 2017西安交大ACM小学期数据结构 [树状数组 离散化]

    Problem E 发布时间: 2017年6月28日 12:53   最后更新: 2017年6月29日 21:35   时间限制: 1000ms   内存限制: 64M 描述 给定一个长度为n的序列a ...

  4. 2017西安交大ACM小学期数据结构 [树状数组,极大值]

    Problem D 发布时间: 2017年6月28日 10:51   最后更新: 2017年6月28日 16:38   时间限制: 1000ms   内存限制: 32M 描述 给定一个长度为n的序列a ...

  5. 2017西安交大ACM小学期数据结构 [树状数组]

    Problem C 发布时间: 2017年6月28日 11:38   最后更新: 2017年6月28日 16:38   时间限制: 1000ms   内存限制: 32M 描述 给定一个长度为n的序列a ...

  6. 数据结构 —— 树状数组

    [概述] 树状数组又称二叉索引树,常用于高效计算数列的前缀和.区间和,其查询.修改的时间复杂度为 log(n),空间复杂度为 O(n) 树状数组通过将线性结构转化成树状结构,从而进行跳跃式扫描. 优点 ...

  7. Java数据结构-树状数组

    什么是树状数组?[面试5.0] 使用数组表示多叉树的结构,和优先队列有点类似,区别在于优先队列只表示二叉树 主要用来: 更新数组元素的数值并且求数组前K个元素的总和或平均值 时间复杂度为O(logN) ...

  8. “高级”数据结构——树状数组

    数据结构是每一个程序员都必须要学的,非常重要! 大佬的博客写的真好,虽然我看不懂..... 链接http://www.cnblogs.com/RabbitHu/p/BIT.html

  9. 树状数组(单点+区间的所有操作)

    转载:https://blog.csdn.net/I_believe_CWJ/article/details/80374326 更简洁方便的数据结构--树状数组(基于线段树的实现) 1.单点更新+区间 ...

最新文章

  1. JSTL标签库的一些基础实例
  2. android自定义view获取控件,android 自定义控件View在Activity中使用findByViewId得到结果为null...
  3. 纯数学教程 Page 203 例XLI (1)
  4. 技术:常见视频会议网络线路介绍
  5. Java单例模式代码实现方式
  6. android读取raw文件示例
  7. C++求一个整数的各位数字总和(附完整源码)
  8. Spring_HelloWord
  9. WAM计算机术语,最新的计算机一级考试MSOffice模拟试题及答案
  10. Linux 如何通过某一台服务器调用执行多台远程服务器上的脚本,结果显示在本地?...
  11. 学UI设计好不好找工作?零基础能学UI设计吗?
  12. 携程实时计算平台架构与实践丨DataPipeline
  13. 计算机应用基础课改期望,《计算机应用基础》课改总结.doc
  14. Xshell出现要继续使用此程序必须应用到最新的更新或使用新版本
  15. JAVA 大数运算模板 ACM竞赛必备
  16. vue手机端回退_vue移动端弹框组件 - osc_eiolhkks的个人空间 - OSCHINA - 中文开源技术交流社区...
  17. Java8新特性详解
  18. 计算机DSU,研科d5000dsu驱动
  19. 什么是Winsock WSAData ?
  20. fwr310刷openwrt_迅捷FWR310无线路由器的刷机

热门文章

  1. [Java] 内部类总结
  2. 【MyBatis笔记】0-MyBatis简介
  3. 人人都在讲数据治理而不问业务,这很危险
  4. 数据体系建设的开端,该如何规划平台?
  5. 求剁手的分享,如何简单开发js图表
  6. 2020运动相机推荐_2020年优秀户外运动相机推荐
  7. python 卡方检验_【技术】卡方检验及其Python实现
  8. 计算机驱动伺服的程序,伺服调试软件V-ASSISTANT始终找不到驱动-工业支持中心-西门子中国...
  9. 在java中使用关键字导入包_java的import关键字的使用
  10. 计算机网络 第一章 计算机网络体系结构