• 树状数组及扩展


    树状数组:顾名思义,就是用数组来模拟树形结构。那么衍生出一个问题,为什么不直接建树?答案是没必要,因为树状数组能处理的问题就没必要建树。

    树状数组能解决什么问题:可以解决大部分基于区间上的更新以及求和问题。

    1.快速求前缀和O(logn)  2.修改某一个数O(logn)

    如果用普通的数组存每个数 1操作就是O(n) 2操作是O(1)的

    如果维护前缀和数组 那么1操作是O(1) 2操作是O(n) 

    扩展:联系差分和推公式

    差分的扩展:树状数组原来的操作1.快速求前缀和O(logn)  2.修改某一个数O(logn)

    现在反过来1.修改某一段区间 2.求某个值 这就联系差分了

    与线段树的联系:树状数组可以解决的问题都可以用线段树解决,这两者的区别在哪里呢?树状数组的系数要少很多,就比如字符串模拟大数可以解决大数问题,也可以解决1+1的问题,但没人会在1+1的问题上用大数模拟。(打小兵用大刀)

    详情

    基本原理:

    首先先定义a[i]为每个元素的值

    然后假定我们要求的是1~x的总和 也就是前x个数的和

    k>k-1>k-2>......>i

    将x用二进制表示 x就可以等于2^k+2^(k-1)+2^(k-2)+.....+2^i 那么我们是不是可以求出前2^k个数 然后再去掉2^k个数 再取去掉2^k个数的前2^(k-1)个数 一直循环

    example: ()为多少进制 

    12(10)=1100(2)=1000(2)+100(2)=8(10)+4(10)12(10)=1100(2)=1000(2)+100(2)=8(10)+4(10)

    是不是先取了前8个数(1-8) 再去除前八个数 选前四个数(9-12)

    那么我们现在将前x个元素分为很多个区间去求和

    1.( x-2^i ,x] 里面有 2^i 个元素

    2.(x-2^(i)-2^(i+1),x-2^(i) ] 里面有2^(i+1)个元素

    ....

    3.( 0 , x - 2^i - 2^(i+1)- ..... - 2^(k-1) ] 里面有 2^(k)个元素

    然后可以发现区间( L,R ] 的长度一定是R二进制表示的最后一位 1

    这时候我们就可以定义一个C数组

    C数组的定义是  以 x 为右端点的长度为lowbit(x)的区间等价于以x结尾 长度为2^k( 末尾有k个0等价于lowbit(x) )的区间和

    那么C[x] = a[ x-lowbit(x)+1 到 x ]的和

    然后这段区间和一定包含 a[ x ] 那么 C[x] = a[x] +a[x - lowbit(x) +1 到 x-1 ]的和

    由于x的形式是......01000这种形式 那么x-1就是......00111

    那么那么我们现在就可以继续分段了

    1. (....00110 到 ...00111] 这个是C[x-1]

    2. (....00100 到 ...00110] 这个是C[(x-1)-lowbit(x-1)]

    3. (....00000 到 ...00100]  这个是C[(x-1)-lowbit(x-1)-lowbit[(x-1)-lowbit[x-1]]]

    那么把这几个全部加起来就是C[x]了

    那么更新操作怎么办呢?

    我要修改a[x] 那么我就得修改包含x的c数组

    假设x为......01100....那么父节点就是......10000 那么修改就是每次加lowbit


    1. 楼兰图腾

    241. 楼兰图腾 - AcWing题库

    tr表示这个位置有多少个这个数

    1. #include
    2. using namespace std;
    3. const int N=200010;
    4. int n,m;
    5. int tr[N];
    6. int a[N];
    7. int great[N],low[N];
    8. int lowbit(int x)
    9. {
    10. return x&-x;
    11. }
    12. void add(int x,int c)
    13. {
    14. for(int i=x;i<=n;i+=lowbit(i))
    15. {
    16. tr[i]+=c;
    17. }
    18. }
    19. int sum(int x)
    20. {
    21. int res=0;
    22. for(int i=x;i;i-=lowbit(i))
    23. {
    24. res+=tr[i];
    25. }
    26. return res;
    27. }
    28. int main()
    29. {
    30. scanf("%d",&n);
    31. for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    32. for(int i=1;i<=n;i++)
    33. {
    34. int y=a[i];
    35. great[i]=sum(n)-sum(y);
    36. low[i]=sum(y-1);
    37. add(y,1);
    38. }
    39. memset(tr,0,sizeof tr);
    40. long long res1=0,res2=0;
    41. for(int i=n;i;i--)
    42. {
    43. int y=a[i];
    44. res1+=great[i]*(long long)(sum(n)-sum(y));
    45. res2+=low[i]*(long long)(sum(y-1));
    46. add(y,1);
    47. }
    48. printf("%lld %lld\n",res1,res2);
    49. return 0;
    50. }

    2.一个简单的整数问题(差分)

    242. 一个简单的整数问题 - AcWing题库

    把tr数组当成差分数组即可

    假如[l,r]+c,则b[l]+=c,b[r+1]-=c;

    假如求x的数是多少,相当于求b[x]的前缀和,然后可以直接用树状数组求用sum(x)即可

    1. #include
    2. using namespace std;
    3. typedef long long ll;
    4. const int N=100010;
    5. int n,m;
    6. int a[N];
    7. ll tr[N];
    8. int lowbit(int x)
    9. {
    10. return x&-x;
    11. }
    12. void add(int x,int c)
    13. {
    14. for(int i=x;i<=n;i+=lowbit(i))
    15. tr[i]+=c;
    16. }
    17. ll sum(int x)
    18. {
    19. ll res=0;
    20. for(int i=x;i;i-=lowbit(i))
    21. {
    22. res+=tr[i];
    23. }
    24. return res;
    25. }
    26. int main()
    27. {
    28. scanf("%d%d",&n,&m);
    29. for(int i=1;i<=n;i++)
    30. scanf("%d",&a[i]);
    31. for(int i=1;i<=n;i++)
    32. add(i,a[i]-a[i-1]);
    33. while(m--)
    34. {
    35. char op[2];
    36. int l,r,d;
    37. scanf("%s%d",op,&l);
    38. if(op[0]=='C')
    39. {
    40. scanf("%d%d",&r,&d);
    41. add(l,d),add(r+1,-d);
    42. }
    43. else
    44. {
    45. printf("%lld\n",sum(l));
    46. }
    47. }
    48. return 0;
    49. }

    3.一个简单的整数问题2(差分+分析)

    243. 一个简单的整数问题2 - AcWing题库

    1. #include
    2. using namespace std;
    3. const int N=100010;
    4. typedef long long ll;
    5. int n,m;
    6. ll tr1[N];
    7. ll tr2[N];
    8. int a[N];
    9. int lowbit(int x)
    10. {
    11. return x&-x;
    12. }
    13. void add(ll tr[],int x,ll c)
    14. {
    15. for(int i=x;i<=n;i+=lowbit(i))
    16. {
    17. tr[i]+=c;
    18. }
    19. }
    20. ll sum(ll tr[],int x)
    21. {
    22. ll res=0;
    23. for(int i=x;i;i-=lowbit(i))
    24. {
    25. res+=tr[i];
    26. }
    27. return res;
    28. }
    29. ll prefix_sum(int x)
    30. {
    31. return sum(tr1,x)*(x+1)-sum(tr2,x);
    32. }
    33. int main()
    34. {
    35. scanf("%d%d",&n,&m);
    36. for(int i=1;i<=n;i++)
    37. {
    38. scanf("%d",&a[i]);
    39. }
    40. for(int i=1;i<=n;i++)
    41. {
    42. add(tr1,i,a[i]-a[i-1]);
    43. add(tr2,i,(ll)(a[i]-a[i-1])*i);
    44. }
    45. while(m--)
    46. {
    47. char op[2];
    48. int l,r,d;
    49. scanf("%s%d%d",op,&l,&r);
    50. if(op[0]=='Q')
    51. {
    52. printf("%lld\n",prefix_sum(r)-prefix_sum(l-1));
    53. }
    54. else
    55. {
    56. scanf("%d",&d);
    57. add(tr1,l,d),add(tr2,l,l*d);
    58. add(tr1,r+1,-d),add(tr2,r+1,(r+1)*(-d));
    59. }
    60. }
    61. return 0;
    62. }

    4.谜一样的牛(二分查找)

    244. 谜一样的牛 - AcWing题库

    思路:从n往1看,每次给剩余的数里面第a[i]+1小的数,然后把这个数删掉,一直这样操作即可

    找第a[i]+1小的数:

    1.把每个数初始值为1,表明这个数还没用过,每个前缀和表明了当前数是第几小的数

    2.二分查找第a[i]+1小的数,因为每个数的前缀和是递增的可以二分

    1. #include
    2. using namespace std;
    3. const int N=1e5+10;
    4. int n;
    5. int a[N],tr[N],ans[N];
    6. int lowbit(int x)
    7. {
    8. return x&-x;
    9. }
    10. void add(int x,int c)
    11. {
    12. for(int i=x;i<=n;i+=lowbit(i)) tr[i]+=c;
    13. }
    14. int sum(int x)
    15. {
    16. int res=0;
    17. for(int i=x;i;i-=lowbit(i)) res+=tr[i];
    18. return res;
    19. }
    20. int main()
    21. {
    22. scanf("%d",&n);
    23. for(int i=2;i<=n;i++) scanf("%d",&a[i]);
    24. for(int i=1;i<=n;i++) add(i,1);//初始化初始值,让每个数都为1,表明没有用过
    25. for(int i=n;i;i--)
    26. {
    27. int b=a[i]+1;//找第a[i]+1小的数
    28. int l=1,r=n;
    29. while(l//二分
    30. {
    31. int mid=l+r>>1;
    32. if(sum(mid)>=b) r=mid;//假如前缀和大了,说明当前数不是第b小的数
    33. else l=mid+1;
    34. }
    35. ans[i]=l;//让答案等于当前找到的第b小的数
    36. add(l,-1);//把当前数删除,因为已经用过了
    37. }
    38. for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
    39. return 0;
    40. }

  • 相关阅读:
    零信任策略下K8s安全监控最佳实践(K+)
    含文档+PPT+源码等]精品基于SSM的网上水果生鲜超市商城[包运行成功]计算机毕设Java项目源码
    JS 实现鼠标框选(页面选择)时返回对应的代码或文本内容
    BUUCTF--[V&N2020 公开赛]warmup
    微信小程序多端应用 Donut 多端编译
    自动驾驶车什么时候普及,自动驾驶还有多久普及
    ROS2与turtlebot4仿真入门教程-turtlebot4遥控
    如何做到一套FPGA工程无缝兼容两款不同的板卡?
    Vue3_2024_1天【Vue3创建和响应式,对比Vue2】
    【Node.js】自定义模块
  • 原文地址:https://blog.csdn.net/lee_14141/article/details/127443958