树状数组是用来解决数列多次单点修改和前缀和查询的利器。

首先我们来看问题的原型:

已知一个长度为n(n<=10 0000)的数列,初始值都是零,现在我们要对数列施加两种类型的操作共q(q<=10 0000)次:

1、在数列第i个位置上增加a

2、查询数列前i个数的和

这两种操作可能会交替出现。

思路:

方法1:

用数组存储数列,对于修改直接在相应位置上o(1)完成,对于查询,直接在数组上循环求和o(n),如果求前缀和的次数居多,则时间接近o(qn),很显然会超时。

方法2:

方法1时间主要浪费在求和,我们可以事先预处理前缀和数组s,那么对于查询操作则是o(1),但是对于修改操作,则需要把修改位置后面的s数组元素都修改,时间o(n),如果修改操作居多的话,则总时间接近于o(qn),也超时。

方法3:

树状数组。树状数组也是预处理的思想。

先介绍一个函数lowbit(x),设x的二进制最低位有连续k个0,lowbit(x)的值等于2的k次方。

例如:

lowbit(1)=2^0=1,lowbit(2)=2^1=2,lowbit(3)=2^0=1,lowbit(4)=2^2=4 …

函数怎么实现的,请读者们先摁住好奇心。

方法2中,之所以修改操作是特别慢,是因为预处理太仔细了,导致每次修改操作会影响很多个预处理的值,我们对此加以改进。对于长度为n的数列,也事先预处理n个信息:

S[1]---表示以1结尾,长度为lowbit(1)的区间和

S[2]---表示以2结尾,长度为lowbit(2)的区间和

S[3]---表示以3结尾,长度为lowbit(3)的区间和

……

S[n]---表示以n结尾,长度为lowbit(n)的区间和

有了这n个信息,对于任意的前缀和,都可以通过很少的几个s元素拼凑出来。

例如求前8个元素的前缀和,s[8]就是所求;

求前9个元素的前缀和,s[9]+s[8]就是所求;

求前10个元素的前缀和,s[10]+s[8]就是所求;

求前11个元素的前缀和,s[11]+s[10]+s[8]就是所求;

求前12个元素的前缀和,s[12]+s[8]就是所求;

求前13个元素的前缀和,s[13]+s[12]+s[8]就是所求;

求前14个元素的前缀和,s[14]+s[12]+s[8]就是所求;

求前15个元素的前缀和,s[15]+s[14]+s[12]+s[8]就是所求;

……

为什么呢?

我们用pre[15]表示前15个元素的前缀和。因为S[15]存储的是以15结尾长度为lowbit(15)的一段区间和。所以s[15]就是pre[15]的最后面一段长度为lowbit(15)的和(后缀和),在pre[15]中去除掉s[15],还剩余pre[14](因为15-lowbit(15)=14);即pre[15]=pre[14]+s[15]。

同样的S[14]存储的是以14结尾长度为lowbit(14)的一段区间和,也就是pre[14]的后缀和,在pre[14]中去掉s[14],还剩余pre[12](因为14-lowbit(14)=12);即pre[14]=pre[12]+s[14],把前面式子中pre[14]用pre[12]+s[14]替换,得到pre[15]=pre[12]+s[14]+s[15].

同样的s[12]中存储的是以12结尾长度为lowbit(12)的一段区间和,即pre[12]的最后一段,在pre[12]中去掉s[12]还剩余pre[8](因为12-lowbit(12)=8);即pre[12]=pre[8]+s[12]。所以pre[15]=pre[8]+s[12]+s[14]+s[15].

同样的s[8]中存储的是以8结尾长度为lowbit(8)的一段区间和,即pre[8]的最后一段,在pre[8]中去掉s[8]还剩余pre[0](因为8-lowbit(8)=0);即pre[8]=pre[0]+s[8]。至此结束,因为pre[0]就是0。代入上面的式子,所以pre[15]=s[8]+s[12]+s[14]+s[15].

所以pre[15]可以用4个预处理的s元素拼凑起来,而且这四个元素是很有规律的,首先必然包含s[15],然后下一个是s中下标为15-lowbit(15),即s[14];再然后是s的下标为14-lowbit(14),即s[12],再再然后是s中下标为12-lowbit(12)的元素,即s[8];再再再然后是下标为8-lowbit(8),即s[0],出现0就结束。请大家仔细体会这个规律。

Pre[15]被4个s元素拼接起来,这不是偶然,因为15的二进制串中(1111)恰好有4个1。Pre[x]需要几个s元素,恰好等于x二进制串中1的个数。为什么呢?

因为pre[x]=pre[x-lowbit(x)]+s[x],x-lowbit(x)这个差值的二进制串恰好比x少了最右边的一个1。读者可以在纸上多写几个数验证一下。

要点:只要我们事先预处理出s[1]、s[2]、s[3]、…、s[n],对于任意前缀和pre[x],都能用很少的几个s元素拼接起来(x的二进制串中数字1的个数,不超过logx)。

如果我们修改数列某一个元素,则肯定会影响s数组的元素,所有必须对s数组有相应的修改。

例如修改数列第一个元素,因为s[1]后缀和包含数列第一个元素,所以需要修改s[1],同样s[2]、s[4]、s[8]、s[16]、…他们都包含了第一个元素,这些s数组元素都要修改。需要修改的元素1-->2-->4-->8-->16-->…有什么联系呢?

如果修改第二个元素,因为s[2]后缀和包含第数列二个元素,所以需要修改s[2],同样s[2]、s[4]、s[8]、s[16]、…他们都包含了数列第二个元素,这些s数组元素都要修改。需要修改的元素2-->4-->8-->16-->…有什么联系呢?

如果修改第三个元素,因为s[3]后缀和包含数列第三个元素,所以需要修改s[3],同样s[4]、s[8]、s[16]、…他们都包含了数列第三个元素,这些s数组元素都要修改。需要修改的元素3-->4-->8-->16-->…有什么联系呢?(请读者思考几分钟,为什么是只修改这些点?)

要点:如果修改数列第x个元素的值,则s[x]要修改,s[x+lowbit(x)]要修改,……,直到超出n为止。总共个数不超过logn。

总结:查询一个前缀和,最多需要查询logn个s数组元素,修改一个数列元素,最多需要修改logn个数组元素,取得一个很平衡的时间效率,非常不错。树状数组非常适合求数列元素修改和区间查询反复出现的问题。

为什么取名字叫树状数组呢?

我们修改每个数列元素时,要修改的s数组元素链条都串起来,如下图构成一个树形,所以取名叫树状数组。

红色方框是s数组元素,黑色方框是数列元素。修改一个数列元素,要修改从他开始的链条,直到超出n为止。

最后一个问题,怎么求lowbit(x)? x&-x就是答案!

设x的二进制为(假设最低位有3个0):  XXXXX1000

则x的反码是:                           XXXXX0111(红色与上面黑色的x相反)

X的反码+1,恰好是-x的补码:           XXXXX1000

所以x&-x,红色和黑色的X做与运算后恰好为0,结果就是1000.对应十进制就是2^3.

详细代码如下:

int lowbit(int x)

{

return x&-x;

}

int getsum(int x)

{

int tot=0;

while(x>0)

{

tot+=s[x];

X-=lowbit(x);

}

return tot;

}

void change(int x,int d)

{

While(x<=n)

{

S[x]+=d;

x+=lowbit(x);

}

}

树状数组求逆序对_初识树状数组相关推荐

  1. 树状数组求逆序对_算法系列之-数组中的逆序对

    题目来源 剑指offer 01 题目描述 在数组中如果前一个数字大于后一个数字,则称为这个数字组合组成一个逆序对.输入一个数组,求所有的逆序对的总数. 如 数组 {7,5,6,4} 则它的逆序对是 ( ...

  2. 树状数组求逆序对_区间和的个数(树状数组)

    327. 区间和的个数 给定一个整数数组 nums,返回区间和在 [lower, upper] 之间的个数,包含 lower 和 upper. 区间和 S(i, j) 表示在 nums 中,位置从 i ...

  3. c语言数组求逆序对,LeetCode 面试题51. 数组中的逆序对

    面试题51. 数组中的逆序对 题目来源:https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/ 题目 在数组中的两个数字,如果 ...

  4. Tido 习题-二叉树-树状数组求逆序对

    这里给大家提供一个全新的求逆序对的方法 是通过树状数组来实现的 题目描述   样例输入 Copy 5 2 3 1 5 4 样例输出 Copy 3 提示       #include<iostre ...

  5. 牛客练习赛33 D tokitsukaze and Inverse Number (树状数组求逆序对,结论)

    链接:https://ac.nowcoder.com/acm/contest/308/D 来源:牛客网 tokitsukaze and Inverse Number 时间限制:C/C++ 1秒,其他语 ...

  6. [USACO17FEB] Why Did the Cow Cross the Road I P (树状数组求逆序对 易错题)

    题目大意:给你两个序列,可以序列进行若干次旋转操作(两个都可以转),对两个序列相同权值的地方连边,求最少的交点数 记录某个值在第一个序列的位置,再记录第二个序列中某个值 在第一个序列出现的位置 ,求逆 ...

  7. Python(分治算法)问题 F: 求逆序对_给定一个序列a1,a2,…,an,如果存在i<j并且ai>aj,那么我们称之为逆序对,求逆序对的数目。

    问题 F: 求逆序对 题目描述 给定一个序列a1,a2,-,an,如果存在i<j并且ai>aj,那么我们称之为逆序对,求逆序对的数目. 注意:n<=10^5,ai<=10^5 ...

  8. 牛客 - What Goes Up Must Come Down(树状数组求逆序对)

    题目链接:点击查看 题目大意:给出一个长度为 n 的序列,每次操作可以交换相邻两个数字的位置,现在问最小进行多少次操作,可以使得序列的相对大小呈山峰状(中间高两边低,或非严格递增或非严格递减) 题目分 ...

  9. 【Luogu1908】逆序对(离散化,树状数组求逆序对)

    problem 给你一个长为n的序列A[] 求该序列的逆序对个数 solution 用b[i]保存下标i在A中出现的次数,那么数组b[i]在[l,r]上的区间和,就表示序列A在范围[l,r]内的有多少 ...

最新文章

  1. 读书笔记6pandas简单使用
  2. Unbuntu 下安装node 环境
  3. 特殊权限位set_uid set_gid   stick_bit 软链接硬链接
  4. Memcached简介
  5. flask连接mysql数据库_Flask与Mysql数据库建立连接
  6. 数电课设—智力抢答器(五路抢答器),已在Proteus8.9版本中仿真通过
  7. 多功能扩音器索爱S-318,教师、导游们的辅助神器
  8. 基于公众号扫码授权登录
  9. Tableau超市案例分析
  10. MIT 18.01 Single Variable Calculus(单变量微积分)课堂笔记【3】——求导四则运算和三角函数求导
  11. 理解OAuth 2.0
  12. CSR BC417143BGQ蓝牙模块芯片替换方案
  13. 啊哈添柴挑战Java1016. 反向输出一个三位数
  14. c#生成PPT总结(Microsoft.Office.Interop)
  15. ROS开发实践-QT工具箱
  16. 计算机中班音乐,【精品】中班音乐教案6篇
  17. 有数字要生成条形码生成器_如何制作自己的“意外”数字生成器
  18. 懒到骨子里了,我在CSDN写文章都懒得自己写了,基于selenium模拟写文章
  19. 华为瘦胖ap互转_Cisco 胖AP转为瘦AP
  20. 图像入门——1. 图像与数字图像介绍

热门文章

  1. 解决SecureCRT中文显示乱码
  2. [Lua]50行代码的解释器,用来演示lambda calculus
  3. jquery选择器 之 获取父级元素、同级元素、子元素
  4. SO做了Booked之后,一直处理于“已延交”,发运事务处理的活动区变灰
  5. BCH再度领涨,BTC能否及时跟上
  6. 暴涨!BTC忠实粉丝转向BCH为BCH网络添砖加瓦
  7. 由于Coinbase插件,BCH被5000万商家接受
  8. Go语言——怎样导入一个外部的包
  9. 状态机系列学习笔记01
  10. 与servlet Api 的集成