学习笔记:树状数组
发布日期:2021-08-14 17:36:23 浏览次数:10 分类:技术文章

本文共 4093 字,大约阅读时间需要 13 分钟。

一、树状数组是干什么的?

       平常我们会遇到一些对数组进行维护查询的操作,比较常见的如,修改某点的值、求某个区间的和,而这两种恰恰是树状数组的强项!当然,数据规模不大的时候, 对于修改某点的值是非常容易的,复杂度是O(1),但是对于求一个区间的和就要扫一遍了,复杂度是O(N),如果实时的对数组进行M次修改或求和,最坏的 情况下复杂度是O(M*N),当规模增大后这是划不来的!而树状数组干同样的事复杂度却是O(M*lgN),别小看这个lg,很大的数一lg就很小了,这 个学过数学的都知道吧,不需要我说了。申明一下,看下面的文章一定不要急,只需要看懂每一步最后自然就懂了。

二、树状数组怎么干的?

        先看两幅图(网上找的,如果雷同,不要大惊小怪~),下面的说明都是基于这两幅图的,左边的叫A图吧,右边的叫B图:

      是不是很像一颗树?对,这就是为什么叫树状数组了~先看A图,a数组就是我们要维护和查询的数组,但是其实我们整个过程中根本用不到a数组,你可以把它当 作一个摆设!c数组才是我们全程关心和操纵的重心。先由图来看看c数组的规则,其中c8 = c4+c6+c7+a8,c6 = c5+a6……先不必纠结怎么做到的,我们只要知道c数组的大致规则即可,很容易知道c8表示a1~a8的和,但是c6却是表示a5~a6的和,为什么会 产生这样的区别的呢?或者说发明她的人为什么这样区别对待呢?答案是,这样会使操作更简单!看到这相信有些人就有些感觉了,为什么复杂度被lg了呢?可以 看到,c8可以看作a1~a8的左半边和+右半边和,而其中左半边和是确定的c4,右半边其实也是同样的规则把a5~a8一分为二……继续下去都是一分为 二直到不能分,可以看看B图。怎么样?是不是有点二分的味道了?对,说白了树状数组就是巧妙的利用了二分,她并不神秘,关键是她的巧妙!

       她又是怎样做到不断的一分为二呢?说这个之前我先说个叫lowbit的东西,lowbit(k)就是把k的二进制的高位1全部清空,只留下最低位的1,比 如10的二进制是1010,则lowbit(k)=lowbit(1010)=0010(2进制),介于这个lowbit在下面会经常用到,这里给一个非 常方便的实现方式,比较普遍的方法lowbit(k)=k&-k,这是位运算,我们知道一个数加一个负号是把这个数的二进制取反+1,如-10的 二进制就是-1010=0101+1=0110,然后用1010&0110,答案就是0010了!明白了求解lowbit的方法就可以了,继续下 面。介于下面讨论十进制已经没有意义(这个世界本来就是二进制的,人非要主观的构建一个十进制),下面所有的数没有特别说明都当作二进制。

       上面那么多文字说lowbit,还没说它的用处呢,它就是为了联系a数组和c数组的!ck表示从ak开始往左连续求lowbit(k)个数的和,比如 c[0110]=a[0110]+a[0101],就是从110开始计算了0010个数的和,因为lowbit(0110)=0010,可以看到其实只有 低位的1起作用,因为很显然可以写出c[0010]=a[0010]+a[0001],这就为什么我们任何数都只关心它的lowbit,因为高位不起作用 (基于我们的二分规则它必须如此!),除非除了高位其余位都是0,这时本身就是lowbit。

既然关系建立好了,看看 如何实现a某一个位置数据跟改的,她不会直接改的(开始就说了,a根本不存在),她每次改其实都要维护c数组应有的性质,因为后面求和要用到。而维护也很 简单,比如更改了a[0011],我们接着要修改c[0011],c[0100],c[1000],这是很容易从图上看出来的,但是你可能会问,他们之间 有申明必然联系吗?每次求解总不能总要拿图来看吧?其实从0011——>0100——>1000的变化都是进行“去尾”操作,又是自己造的词 --'',我来解释下,就是把尾部应该去掉的1都去掉转而换到更高位的1,记住每次变换都要有一个高位的1产生,所以0100是不能变换到0101的,因 为没有新的高位1产生,这个变换过程恰好是可以借助我们的lowbit进行的,k +=lowbit(k)。

       好吧,现在更新的次序都有了,可能又会产生新的疑问了:为什么它非要是这种关系啊?这就要追究到之前我们说c8可以看作a1~a8的左半边和+右半边 和……的内容了,为什么c[0011]会影响到c[0100]而不会影响到c[0101],这就是之前说的c[0100]的求解实际上是这样分段的区间 c[0001]~c[0001] 和区间c[0011]~c[0011]的和,数字太小,可能这样不太理解,在比如c[0100]会影响c[1000],为什么呢?因为c[1000]可以 看作0001~0100的和加上0101~1000的和,但是0101位置的数变化并会直接作用于c[1000],因为它的尾部1不能一下在跳两级在产生 两次高位1,是通过c[0110]间接影响的,但是,c[0100]却可以跳一级产生一次高位1。

         可能上面说的你比较绕了,那么此时你只需注意:c的构成性质(其实是分组性质)决定了c[0011]只会直接影响c[0100],而c[0100]只会直 接影响[1000],而下表之间的关系恰好是也必须是k +=lowbit(k)。此时我们就是写出跟新维护树的代码:

 

1     void add(int k,int num)  2     {  3         while(k<=n)  4         {  5             tree[k]+=num;  6             k+=k&-k;  7         }  8     }

 

有了上面的基础,说求和就比较简单了。比如求0001~0110的和就直接c[0100]+c[0110],分析方法与上面的恰好逆过来,而且写法也是逆过来的,具体就不累述了:

 

1     int read(int k)//1~k的区间和   2     {   3         int sum=0;   4         while(k)   5         {   6             sum+=tree[k];   7             k-=k&-k;   8         }   9         return sum;  10     }

 

三、总结一下吧

          首先,明白树状数组所白了是按照二分对数组进行分组;维护和查询都是O(lgn)的复杂度,复杂度取决于最坏的情况,也是O(lgn);lowbit这里 只是一个技巧,关键在于明白c数组的构成规律;分析的过程二进制一定要深入人心,当作心目中的十进制。

(参考:

 【模板】{代码}

1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8 #include
9 #include
10 #include
11 #define maxn 100001 12 #define F(i,j,k) for(int i=j;i<=k;i++) 13 #define M(a,b) memset(a,b,sizeof(a)) 14 #define FF(i,j,k) for(int i=j;i>=k;i--) 15 #define inf 0x7fffffff 16 #define maxm 21 17 //#define LOCAL 18 using namespace std; 19 int read(){ 20 int x=0,f=1;char ch=getchar(); 21 while(ch<'0'||ch>'9'){ if(ch=='-')f=-1;ch=getchar();} 22 while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} 23 return x*f; 24 } 25 int n,m; 26 int a[maxn],c[maxn]; 27 int last[maxn]; 28 inline int lowbit(int x) 29 { 30 return x&-x; 31 } 32 inline void add(int k,int num) 33 { //建树&增加常数 34 last[k]+=num; 35 while(k<=n) 36 { 37 c[k]+=num; 38 k+=lowbit(k); 39 } 40 // F(i,1,n)cout<
<<" ";cout<
>n; 93 F(i,1,n){ 94 cin>>a[i]; 95 add(i,a[i]); 96 } 97 /* 98 int opt,x,y,z; 99 while(1)100 {101 cin>>opt;102 switch(opt)103 {104 case 1:cin>>x>>y;add(x,y);break;105 case 2:cin>>x>>y>>z;add_interval(x,y,z);break;106 case 3:cin>>x>>y;change(x,y);break;107 case 4:cin>>x;cout<
<
>x;cout<
<
>x>>y;cout<
<
View Code

 

转载于:https://www.cnblogs.com/SBSOI/p/5647184.html

转载地址:https://blog.csdn.net/weixin_30731287/article/details/95570370 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:洛谷P1772 [ZJOI2006]物流运输 题解
下一篇:一起学Python:字符串介绍

发表评论

最新留言

路过按个爪印,很不错,赞一个!
[***.219.124.196]2024年12月07日 11时43分48秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

java注解验证_JAVA里自定义注解来进行数据验证 2019-06-17
java equals使用_java.中equals的使用 2019-06-17
java集合表_java集合 2019-06-17
python的sorted函数用法_python之sort、sorted函数详解 2019-06-17
python自动出报告_Python自动提取生成博客园年度报告 2019-06-17
java gson对象转json_使用Gson把Java对象转换成Json字符串 | 学步园 2019-06-17
java线程executors_java多线程教程—Executors线程池 2019-06-17
c 获取mysql所有表名称_MFC ODBC 访问 数据库如何获取到所有表名? 2019-06-17
php redis 关闭,使用PHP+Redis实现延迟任务,实现自动取消订单功能 2019-06-17
php字符串相加函数,php 字符串加密函数 2019-06-17
php活体检测,双目活体检测摄像头,人脸识别SDK,红外活体算法 2019-06-17
php 获取图片保存,PHP获取远程图片并保存到本地 2019-06-17
linux apache2.4 php,linux + apache 2.4.4 + php配置 2019-06-17
wordpress customize.php,从wordpress插件覆盖$wp_customize 2019-06-17
java当输入一个数字就进行加一操作,Java第二次作业 - osc_zm0p03yr的个人空间 - OSCHINA - 中文开源技术交流社区... 2019-06-17
lbp与matlab gui实现,LBP之matlab实现 2019-06-17
matlab去除向量的重复点,matlab矩阵中如何去掉重复的行? | 学步园 2019-06-17
matlab gui串口调试,matlab中GUI的串口调试程序(发送与接收,很全面) 2019-06-17
用matlab解方程二分法,matlab 01---解简单方程 方程组 二分法 2019-06-17
matlab工程计算及应用 课程名称,《matlab工程应用》课程教学大纲 2019-06-17