一、树状数组是什么

树状数组,又称二进制索引树,英文名Binary Indexed Tree

之前遇到一个求逆序对的题,看了很多题解都只说了这个树状数组,关于怎么实现的全都避而不谈,我研究了一下午,总算搞出个头绪了

一般用来求前缀和,可以把时间复杂度从O(n)降到O(log10 n)非常恐怖,举个例子,假如我们要求从1~1000的前缀和,普通方法需要遍历1000次,而树状数组只需要遍历5次,

先上图;

又称二进制索引树的几个特点:

  1. 底部确定,顶部无穷大
  2. 最外面的结点是2的n次方,如上图1,2,4,8的结点
  3. 奇数的结点一定是叶子结点
  4. 数组一定要从1开始

图中每一个非叶子节点都代表一个区间的和,

a[4]代表的是a[1]+a[2]+a[3]+a[4],

a[5]就是自己a[5]

例如我们需要求1~11的和,我们就只需要求出a[11] ,a[10] ,a[8]的和,

同样的,假如求1~1000的和,那么只需要求a[1000] ,a[992] ,a[960], a[896], a[768],a[512]的和,

可以看出每次最后的一个结点必定是2的次方

因为只需要跳到最外面的一个结点就不需要在往下求了,而最外面的结点一定是2的n次方,不懂得可以根据那个图模拟一下。

二、树状数组怎么实现:

lowbit:

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

相信很多童鞋看到这个东西都是一脸懵逼,而我就不一样了,我TM是十脸懵逼,那让我们来推测一下:

首先根据英语意思理解,low就是低,bit就是位,连起来就是最低位,其实就是求的一个数的二进制的第一个1的位置,

比如6,它的二进制:0110,从右往左第一个1的位置是1(从零开始),那么lowbit(6)就等于2的1次方就等于2,

再比如9,它的二进制:1001,从右往左第一个1的位置是0,那么lowbit(6)就等于2的0次方就等于1,

我们再来看一下实现原理:

x&-x到底是个什么玩意

要知道,二进制数的负数是正数取反加一,那么-6就是1010,-6&6 就是 1010 & 0110==0010,换算成十进制就是2,所以lowbit(6)==2

现在实现原理知道了,那么这玩意跟树状数组有什么关系,请听下回讲解:

lowbit(x)是用来跳到x的下一个结点或者上一个结点的的介质,

比如lowbit(6)==2,那么上一个结点就是6-lowbit(6)==4,下一个结点是是6+lowbit(6)==8,可以看一下上面的那个图

这里说的上一个结点不是父节点,而是你要求的前缀和的管辖区间的结点,比如我们要求1~6的和:

  1. 首先x=6,sum+=a[6],上图中a[6]代表的是a[5]+a[6]
  2. x=6-lowbit(6)==4,sum+=a[4],a[4]代表的是a[1]+a[2]+a[3]+a[4]
  3. x=4-lowbit(4)==0,0小于1不符合条件了,退出

此时sum==[1]+a[2]+a[3]+a[4]+a[5]+a[6],是不是很完美

如果我们要更新某一个点的值,那么所有把这个点包含在内的区间结点都需要更新,比如我们要使a[6]+1:

  1. 首先x=6,a[6]++
  2. x=6+lowbit(6)==8,a[8]++
  3. x=8+lowbit(8)==16, a[16]++
  4. .........直到x>n,(n是整个序列的长度)

由于这个树是没有上界的,题目一般都会给出一个序列的长度为n

是不是感觉很强大,我只能这样说明它,不懂怎么证明它。orz

关于为什么只能求前缀和,不能求区间和,可以仔细看一下上图很快就会明白,如果一旦到最外面的结点(2的n次方),那么x必定等于lowbit(x),x-lowbit(x)必定等于0,就会跳出累加,而最外面的结点x,一定代表的是1~x的和,就是前缀和,如果求区间和只能拿右边界的前缀和减去左边界的前缀和了

但是这个结构还有一个缺点,就是a[8]代表的是a[1~8]的和,所以我们并不知道a[8]具体是多少,要避免这种情况,就需要另外开一个与原序列长度相同的数组 t,用来存放树,原数组不动,

根据上图,a数组是最下面的一排叶子结点,用 t 数组来存储除叶子结点的树,

细心的同学可以发现,树结点的序号都是偶数,奇数的叶子结点都是一个单独的区间

如果要查询区间和的话,只需要判断t中的这个结点是不是偶数或者是不是等于0,这里就不再bb了

怎么求逆序对

逆序对的定义:i<j&&a[i]>a[j]

要求一个数组的逆序对,可以用归并排序的概念加一个累加就行,具体看这个例子洛谷P1908 逆序对_卑鄙的我-CSDN博客,我们这里主要介绍的是树状数组求逆序对

首先明白一个概念叫做离散化(Discretization)

在上面介绍的树状数组中,只需要开一个与原序列中最大元素相等的长度数组就行,那么如果我的序列是1,5,3,8,999,本来5个元素,却需要开到999这么大,造成了巨大的空间浪费,

离散化就是另开一个数组,d, d[i]用来存放第i大的数在原序列的什么位置,比如原序列a={5,3,4,2,1},第一大就是5,他在a中的位是1,所以d[1]=1,同理d[2]=3,········所以d数组为{1,3,2,4,5},

转换之后,空间复杂度就没这么高了,但不是求d中的逆序对了,而是求d中的正序对,来看一下怎么求的:

  1. 首先把1放到树状数组t中,此时t只有一个数1,t中比1小的数没有,sum+=0
  2. 再把3放到树状数组t中,此时t只有两个数1,3,比3小的数只有一个,sum+=1
  3. 把2放到树状数组t中,此时t只有两个数1,2,3,比2小的数只有一个,sum+=1
  4. 把4放到树状数组t中,此时t只有两个数1,2,3,4,比4小的数有三个,sum+=3
  5. 把5放到树状数组t中,此时t只有两个数1,2,3,4,5,比5小的数有四个,sum+=4

最后算出来,总共有9个逆序对,可以手算一下原序列a,也是9个逆序对,

具体实现:

  1. 另d[1]=1, d[2]=2········a[n]=n
  2. 根据原数组a中的元素的大小进行排序

实现代码:

#include<bits/stdc++.h>
#define M 500005
using namespace std;
int a[M],d[M],t[M],n;
//原数组/ 离散化后的数组/ 树状数组
bool cmp(int x,int y)
{if(a[x]==a[y]) return x>y;//避免元素相同 return a[x]>a[y];//按照原序列第几大排列
}
int main()
{cin>>n;for(int i=1;i<=n;i++)cin>>a[i],d[i]=i;//初始化sort(d+1,d+n+1,cmp);//排序时候d就是离散化的数组了 return 0;
} 

离散化之后,就是求和了,

根据上面的步骤每一次把一个新的数x放进去之后,都要求比他小的元素有几个,而比他小的元素个数一定是1到x中存在数的个数,也就是[1 , x-1]中有几个数,是不是很耳熟,有点像之前讲的前缀和了,只不过树状数组t表是的不是前缀和了,t[x]表示的是[1,x]中有几个数已经存在,这样我们每次把一个新的数x放进去的时候,都需要把包含这个数的结点更新,然后查询[1,x-1]有几个数已经存在。

还是拿上一个例子:

  1. 把1放进去,包含t[1]的结点t[1]++,t[2]++、t[4]++, 由于n==5,算到t[8]的时候就已经跳出,查询[1,1-1]中比他小的数为0
  2. 把3放进去, 包含t[3]的结点t[3]++,  t[4]++  ,然后查询[1 , 3-1]中有几个数已经存在,t[2]==1, sum+=1,lowbit一发等于0跳出
  3. 把2放进去, 包含t[2]的结点t[2]++,  t[4]++ , 然后查询[1 , 2-1]中有几个数已经存在,t[1]==1, sum+=1,lowbit一发等于0跳出
  4. 把4放进去,包含t[4]的结点t[4]++,   t[8]大于n 跳出, 查询[1 , 4-1]中有几个数已经存在,t[3]==1, sum+=1,lowbit一发t[2]==2,所以sum+=2
  5. 把5放进去, 包含t[5]的结点t[5]++,   t[6]大于n跳出 , 查询[1 , 5-1]中有几个数已经存在,t[4]==4, sum+=4,lowbit一发等于0跳出

最后答案就出来了,关键是要理解那句标了红色的那句话,不是前缀和,而是有几个数已经存在,假如a[8]等于4,那么就表示[1,8]中只有4个数存在。

完整代码:

#include<bits/stdc++.h>
#define M 500005
using namespace std;
int a[M],d[M],t[M],n;
int lowbit(int x)
{return x&-x;
}
int add(int x)//把包含这个数的结点都更新
{while(x<=n)//范围 {t[x]++;x+=lowbit(x);}
}
int sum(int x)//查询1~X有几个数加进去了
{int res=0;while(x>=1){    res+=t[x];x-=lowbit(x);}return res;
}
bool cmp(int x,int y)//离散化比较函数
{if(a[x]==a[y]) return x>y;//避免元素相同 return a[x]>a[y];//按照原序列第几大排列
}
int main()//402002139
{//freopen("in.txt","r",stdin);long long ans=0;cin>>n;for(int i=1;i<=n;i++)cin>>a[i],d[i]=i;sort(d+1,d+n+1,cmp);//离散化 for(int i=1;i<=n;i++){add(d[i]);//把这个数放进去 ans+=sum(d[i]-1);//累加 }cout<<ans;return 0;
}

求前缀和版本:

#include<bits/stdc++.h>
#define lowbit(x) (x&-x)
using namespace std;const int N = 1000;
int t[N<<2], n;void add(int x, int id)
{for(int i = id; i <= n; i += lowbit(i))t[i] += x;
}int sum(int id)
{int res = 0;for(int i = id; i > 0; i-= lowbit(i))res += t[i];return res;
}int main()
{n = 10;for(int i = 1; i <= 10; i++){int x;cin >> x;add(x, i);}while(1){int x;cin >> x;cout << sum(x) << endl;}return 0;
}

不要以为那个while循环会执行很多次,就算x==1000,也只会遍历5次!!!,

天才创造了世界~

码字不易,点个赞再走呗

树状数组(求逆序对)相关推荐

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

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

  2. loj #535. 「LibreOJ Round #6」花火 树状数组求逆序对+主席树二维数点+整体二分...

    $ \color{#0066ff}{ 题目描述 }$ 「Hanabi, hanabi--」 一听说祭典上没有烟火,Karen 一脸沮丧. 「有的哦-- 虽然比不上大型烟花就是了.」 还好 Shinob ...

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

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

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

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

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

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

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

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

  7. 树状数组 求 逆序对

    如何统计第i个数与1-第i - 1个数构成多少个逆序对呢? 考虑根据值来建树状数组,初始树状数组为全0,先按序列从左到右将数据的值对应的位置的数加一,代表又有一个数出现.因此,在循环到第i项时,前i- ...

  8. 树状数组求逆序对_初识树状数组

    树状数组是用来解决数列多次单点修改和前缀和查询的利器. 首先我们来看问题的原型: 已知一个长度为n(n<=10 0000)的数列,初始值都是零,现在我们要对数列施加两种类型的操作共q(q< ...

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

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

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

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

最新文章

  1. HEOI2012 朋友圈
  2. django一个html先后两个form,django 一个页面两个表单 怎么提交
  3. 「预警」尽快升级FastJson版本,避免恶意请求导致OOM!
  4. VC++读写INI文件示例
  5. 【强化学习】一文带你理清强化学习
  6. mysql高级查询教程_MYSQL高级查询
  7. 【UML】协作图Collaboration diagram(交互图)(转)
  8. 软件项目经理的心得和经验总结
  9. php文件之间传值的三种主流并且常用的方式
  10. 【渝粤教育】国家开放大学2018年春季 0063-21T中国现当代文学专题 参考试题
  11. 高品味男人:分寸与克制
  12. oracle文件名乱码,如何rename datafile name中存在乱码的数据文件
  13. CRNN——卷积循环神经网络结构
  14. excel有条件的隐藏某行_暂时隐藏Excel条件格式
  15. java队列和栈 共同_java 栈和队列的模拟--java
  16. android 刷机 备份,盘点和对比 Android刷机前的数据备份
  17. 微信小程序-知晓云等云产品导出excel
  18. sql 节假日判断(春节、中秋、国庆、周末等)
  19. matlab外推法求区间
  20. matlab学习增强学习,使用 MATLAB 和 Simulink 进行强化学习

热门文章

  1. vue 生命周期 返回不触发_Vue生命周期小结
  2. python URL解码成UTF-8
  3. 人工智能 | ShowMeAI资讯日报 #2022.06.30
  4. 6.5——ADRC学习
  5. mkl的静态库还有动态库的构成
  6. 【centOS】【xshell】xshell连接虚拟机上的centOS,操作途中突然断开连接,报错:connect closed by foreign host...
  7. 偏度和峰度描述什么_什么是偏度和峰度
  8. 西蒙的管理决策学派(1977)--轉載
  9. HDU1007(Quoit Design)
  10. 洛谷P1983 车站分级(拓扑排序)