当前位置:   article > 正文

高级数据结构—树状数组

高级数据结构—树状数组

引入问题:

给出一个长度为n的数组,完成以下两种操作:
1. 将第i个数加上k

2. 输出区间[i,j]内每个数的和

朴素算法:

单点修改:O( 1 )

区间查询:O( n )

使用树状数组:

单点修改:O( logn )

区间查询:O( logn )

前置知识:

lowbit()
运算:非负整数x在二进制表示下最低位1及其后面的0构成的数值。

如lowbit(12)= 1100 = 4

函数实现: 

  1. int lowbit(int x){
  2. return x&(-x);
  3. }

树状数组思想:

树状数组的主要思想就是利用树结构维护“前缀和”。

对于一个序列,对其建立如下树形结构:

1.每个结点t[x]保存以x为根的子树中叶结点值的和
2.每个结点覆盖的长度为lowbit(x)
3.t[x]结点的父结点为t[x + lowbit(x)]
4.树的深度为log2n+1

树状数组操作:

add(x, k)表示将序列中第x个数加上k。

在整棵树上维护这个值,需要一层一层向上找到父结点,并将这些结点上的t[x]值都加上k,这样保证计算区间和时的结果正确。

  1. void add(int x, int k)
  2. {
  3. for(int i = x; i <= n; i += lowbit(i))
  4. t[i] += k;
  5. }

ask(x)表示将查询序列前x个数的和

以ask(7)为例:
查询这个点的前缀和,需要从这个点向左上找到上一个结点,将加上其结点的值。向左上找到上一个结点,只需要将下标 x -= lowbit(x),例如 7 - lowbit(7) = 6

  1. int ask(int x)
  2. {
  3. int sum = 0;
  4. for(int i = x; i; i -= lowbit(i))
  5. sum += t[i];
  6. return sum;
  7. }

例题: 

楼兰图腾

题目链接:楼兰图腾

从左向右依次遍历每个数a[i],使用树状数组统计在i位置之前所有比a[i]大的数的个数、以及比a[i]小的数的个数。
统计完成后,将a[i]加入到树状数组。

从右向左依次遍历每个数a[i],使用树状数组统计在i位置之后所有比a[i]大的数的个数、以及比a[i]小的数的个数。
统计完成后,将a[i]加入到树状数组。

代码附上:

  1. #include <bits/stdc++.h>
  2. #define int long long
  3. using namespace std;
  4. const int N = 2e5+5;
  5. int tr[N];
  6. int low[N],great[N];
  7. int a[N];
  8. int n;
  9. int lowbit(int x){
  10. return x&(-x);
  11. }
  12. void add(int x,int c){
  13. for(int i=x;i<=n;i+=lowbit(i)){
  14. tr[i]+=c;
  15. }
  16. return;
  17. }
  18. int ask(int x){
  19. int res=0;
  20. for(int i=x;i>=1;i-=lowbit(i)){
  21. res+=tr[i];
  22. }
  23. return res;
  24. }
  25. signed main(){
  26. ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
  27. cin>>n;
  28. for(int i=1;i<=n;i++)cin>>a[i];
  29. for(int i=1;i<=n;i++){//记录i点左边的信息
  30. int y=a[i];
  31. low[i]=ask(y-1);//low[i]记录左边[1,y-1]区间的数字个数
  32. great[i]=ask(n)-ask(y);//great[i]记录左边[y+1,n]区间的数字个数
  33. add(y,1);//加入树状数组中,出现一次+1
  34. }
  35. memset(tr,0,sizeof(tr));//初始化
  36. int ans1=0,ans2=0;
  37. for(int i=n;i>=1;i--){//记录i点右边的信息
  38. int y=a[i];
  39. ans2+=low[i]*ask(y-1);//乘法原理
  40. ans1+=great[i]*(ask(n)-ask(y-1));
  41. add(y,1);
  42. }
  43. cout<<ans1<<" "<<ans2<<"\n";
  44. return 0;
  45. }

一个简单的整数问题

题目链接:一个简单的整数问题

区间修改 + 求单点

那么这道题目可以采用差分的方法去做,在上一道题目中我们求的是区间和,采用前缀和的思想去做,那么这里我们可以把单点a[i]看成前缀和,因为a[i]=\sum差分和。

首先我们要先建差分数组:

  1. for(int i=1;i<=n;i++){
  2. add(i,a[i]-a[i-1]);
  3. }

 那么接下来的情况就跟第一题一样了

代码附上:

  1. #include <bits/stdc++.h>
  2. #define int long long
  3. using namespace std;
  4. const int N =1e5+5;
  5. int a[N],tr[N],low[N],great[N];
  6. int n,q;
  7. int lowbit(int x){
  8. return x&-x;
  9. }
  10. void add(int x,int c){
  11. for(int i=x;i<=n;i+=lowbit(i)){
  12. tr[i]+=c;
  13. }
  14. return;
  15. }
  16. int ask(int x){
  17. int res=0;
  18. for(int i=x;i>=1;i-=lowbit(i)){
  19. res+=tr[i];
  20. }
  21. return res;
  22. }
  23. signed main(){
  24. ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
  25. cin>>n>>q;
  26. for(int i=1;i<=n;i++)cin>>a[i];
  27. for(int i=1;i<=n;i++){
  28. add(i,a[i]-a[i-1]);
  29. }
  30. while(q--){
  31. char op;cin>>op;
  32. if(op=='C'){
  33. int l,r,d;cin>>l>>r>>d;
  34. add(l,d);
  35. add(r+1,-d);
  36. }
  37. else if(op=='Q'){
  38. int l;cin>>l;
  39. cout<<ask(l)<<"\n";
  40. }
  41. }
  42. return 0;
  43. }

一个简单的整数问题2

题目链接:一个简单的整数问题2

区间查询+区间修改

直接上图:

 所以这道题目我们需要维护两个树状数组,一个是d[i],另一个是i*d[i]。

  1. #include <bits/stdc++.h>
  2. #define int long long
  3. using namespace std;
  4. const int N = 1e5+5;
  5. int n,m;
  6. int a[N];
  7. int tr[N],tr2[N];
  8. //tr[]数组是原始数组的差分数组d[i]的树状数组
  9. //tr2[]数组是原始数组的差分数组乘以i即i*d[i]的树状数组
  10. int lowbit(int x){
  11. return x&-x;
  12. }
  13. void add1(int x,int c){
  14. for(int i=x;i<=n;i+=lowbit(i)){
  15. tr[i]+=c;
  16. }
  17. return;
  18. }
  19. void add2(int x,int c){
  20. for(int i=x;i<=n;i+=lowbit(i)){
  21. tr2[i]+=c;
  22. }
  23. return;
  24. }
  25. int ask1(int x){
  26. int res=0;
  27. for(int i=x;i>=1;i-=lowbit(i)){
  28. res+=tr[i];
  29. }
  30. return res;
  31. }
  32. int ask2(int x){
  33. int res=0;
  34. for(int i=x;i>=1;i-=lowbit(i)){
  35. res+=tr2[i];
  36. }
  37. return res;
  38. }
  39. int get_sum(int x){//最后一步的推导
  40. return ask1(x)*(x+1)-ask2(x);
  41. }
  42. signed main(){
  43. ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
  44. cin>>n>>m;
  45. memset(tr2,0,sizeof(tr2));
  46. memset(tr,0,sizeof(tr));
  47. for(int i=1;i<=n;i++)cin>>a[i];
  48. for(int i=1;i<=n;i++){
  49. int b=a[i]-a[i-1];
  50. add1(i,b);
  51. add2(i,b*i);
  52. }
  53. while(m--){
  54. char op;cin>>op;
  55. if(op=='Q'){
  56. int l,r;cin>>l>>r;
  57. cout<<get_sum(r)-get_sum(l-1)<<"\n";
  58. }
  59. else if(op=='C'){
  60. int l,r,d;cin>>l>>r>>d;
  61. add1(l,d),add1(r+1,-d);
  62. add2(l,l*d),add2(r+1,(r+1)*(-d));
  63. }
  64. }
  65. return 0;
  66. }

谜一样的牛

题目链接:谜一样的牛

仅仅根据题目的信息,很难找出他们之间的高度关系。面对这种题目,我们可以从边界去思考,(正着不行就反着来),从最后一头牛看,最后一头牛的a[i]表示前i头牛有x个比它矮的,那么也就意味着它排第x+1位,那么从倒数第二头牛来看,前面有y头比它矮的,那最后一头牛可能比它高也可能比它矮,因此,对于倒数第二头牛而言,它应该在除去上述x+1的区间[1,n]中,选取A(n−1)+1小的数。其他的牛以此类推。

假如建立一个全部元素为1的数列,某个位置的数为1代表这个高度还不知道是哪头牛的,那么就用树状数组维护该数列的前缀和,若某个位置的前缀和等于A(i)+1此时的下标就是要找的数。选择这个数后,将相应位置的1置0.可以二分这个位置。

  1. #include <bits/stdc++.h>
  2. #define int long long
  3. using namespace std;
  4. const int N = 1e5+5;
  5. int a[N],ans[N],tr[N];
  6. int n;
  7. int lowbit(int x){
  8. return x&-x;
  9. }
  10. void add(int x,int c){
  11. for(int i=x;i<=n;i+=lowbit(i)){
  12. tr[i]+=c;
  13. }
  14. return;
  15. }
  16. int ask(int x){
  17. int res=0;
  18. for(int i=x;i>=1;i-=lowbit(i)){
  19. res+=tr[i];
  20. }
  21. return res;
  22. }
  23. signed main(){
  24. ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
  25. cin>>n;
  26. a[1]=0;
  27. for(int i=2;i<=n;i++)cin>>a[i];
  28. for(int i=1;i<=n;i++)tr[i]=lowbit(i);//初始化
  29. for(int i=n;i>=1;i--){//从后往前枚举
  30. int k=a[i]+1;//找剩余的牛中高度排第k
  31. int l=1,r=n;
  32. while(l<r){
  33. int mid=(l+r)/2;
  34. if(ask(mid)>=k)r=mid;
  35. else l=mid+1;
  36. }
  37. ans[i]=r;
  38. add(r,-1);//高度排第r的牛去掉
  39. }
  40. for(int i=1;i<=n;i++){
  41. cout<<ans[i]<<"\n";
  42. }
  43. return 0;
  44. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/460851
推荐阅读
相关标签
  

闽ICP备14008679号