树状数组求逆序对_初识树状数组
树状数组是用来解决数列多次单点修改和前缀和查询的利器。
首先我们来看问题的原型:
已知一个长度为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);
}
}
树状数组求逆序对_初识树状数组相关推荐
- 树状数组求逆序对_算法系列之-数组中的逆序对
题目来源 剑指offer 01 题目描述 在数组中如果前一个数字大于后一个数字,则称为这个数字组合组成一个逆序对.输入一个数组,求所有的逆序对的总数. 如 数组 {7,5,6,4} 则它的逆序对是 ( ...
- 树状数组求逆序对_区间和的个数(树状数组)
327. 区间和的个数 给定一个整数数组 nums,返回区间和在 [lower, upper] 之间的个数,包含 lower 和 upper. 区间和 S(i, j) 表示在 nums 中,位置从 i ...
- c语言数组求逆序对,LeetCode 面试题51. 数组中的逆序对
面试题51. 数组中的逆序对 题目来源:https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/ 题目 在数组中的两个数字,如果 ...
- Tido 习题-二叉树-树状数组求逆序对
这里给大家提供一个全新的求逆序对的方法 是通过树状数组来实现的 题目描述 样例输入 Copy 5 2 3 1 5 4 样例输出 Copy 3 提示 #include<iostre ...
- 牛客练习赛33 D tokitsukaze and Inverse Number (树状数组求逆序对,结论)
链接:https://ac.nowcoder.com/acm/contest/308/D 来源:牛客网 tokitsukaze and Inverse Number 时间限制:C/C++ 1秒,其他语 ...
- [USACO17FEB] Why Did the Cow Cross the Road I P (树状数组求逆序对 易错题)
题目大意:给你两个序列,可以序列进行若干次旋转操作(两个都可以转),对两个序列相同权值的地方连边,求最少的交点数 记录某个值在第一个序列的位置,再记录第二个序列中某个值 在第一个序列出现的位置 ,求逆 ...
- Python(分治算法)问题 F: 求逆序对_给定一个序列a1,a2,…,an,如果存在i<j并且ai>aj,那么我们称之为逆序对,求逆序对的数目。
问题 F: 求逆序对 题目描述 给定一个序列a1,a2,-,an,如果存在i<j并且ai>aj,那么我们称之为逆序对,求逆序对的数目. 注意:n<=10^5,ai<=10^5 ...
- 牛客 - What Goes Up Must Come Down(树状数组求逆序对)
题目链接:点击查看 题目大意:给出一个长度为 n 的序列,每次操作可以交换相邻两个数字的位置,现在问最小进行多少次操作,可以使得序列的相对大小呈山峰状(中间高两边低,或非严格递增或非严格递减) 题目分 ...
- 【Luogu1908】逆序对(离散化,树状数组求逆序对)
problem 给你一个长为n的序列A[] 求该序列的逆序对个数 solution 用b[i]保存下标i在A中出现的次数,那么数组b[i]在[l,r]上的区间和,就表示序列A在范围[l,r]内的有多少 ...
最新文章
- 读书笔记6pandas简单使用
- Unbuntu 下安装node 环境
- 特殊权限位set_uid set_gid stick_bit 软链接硬链接
- Memcached简介
- flask连接mysql数据库_Flask与Mysql数据库建立连接
- 数电课设—智力抢答器(五路抢答器),已在Proteus8.9版本中仿真通过
- 多功能扩音器索爱S-318,教师、导游们的辅助神器
- 基于公众号扫码授权登录
- Tableau超市案例分析
- MIT 18.01 Single Variable Calculus(单变量微积分)课堂笔记【3】——求导四则运算和三角函数求导
- 理解OAuth 2.0
- CSR BC417143BGQ蓝牙模块芯片替换方案
- 啊哈添柴挑战Java1016. 反向输出一个三位数
- c#生成PPT总结(Microsoft.Office.Interop)
- ROS开发实践-QT工具箱
- 计算机中班音乐,【精品】中班音乐教案6篇
- 有数字要生成条形码生成器_如何制作自己的“意外”数字生成器
- 懒到骨子里了,我在CSDN写文章都懒得自己写了,基于selenium模拟写文章
- 华为瘦胖ap互转_Cisco 胖AP转为瘦AP
- 图像入门——1. 图像与数字图像介绍