• 【洛谷】P3368 【模板】树状数组 2 (单点修改+区间查询或区间修改+单点查询的作用)


    (家人这是我在其他地方借鉴的,真的真的感觉很好就记录分享给大家啦~,也顺便当成个笔记方便回顾(逃~))

    树状数组是用数据压缩的思想由二进制实现的数据结构。有单点修改+区间查询或区间修改+单点查询的作用。

    实现单点修改&区间查询

    首先我们来看看暴力的效率。q组询问,极端情况下n个数的修改,效率为 O(n q) 。n,q为500000时一定会炸。

    切入点:lowbit函数

    由于电脑一种叫做补码的操作(由于电脑是二进制,它们存的相反数是它的取反+1),一个数与它的相反数做与操作时会返回二进制下最右边的1的位置。举个例子: 6&-6=2 将6变成二进制:110。其中最右侧的1加粗字体:110则返回的是二进制下10的值:2。得到代码:

    1. //ll就是long long
    2. ll lowbit(ll num){
    3. return num&-num;
    4. }

    利用这个性质建立树状数组:

    为了简化区间修改的效率,我们需要建立这样的一个数组:数组中第k位的值为原数组中的一段区间和,这个区间的长度是lowbit(k),终点是k。 比如:输入一个数组,那么我们所建立的数组:

    • 第一位(1在二进制下=1 二进制下的1=1)的值为输入的数组的第一位往前的一位的和,也就是第一位。
    • 第二位(2在二进制下=10 二进制下的10=2)的值为输入的数组的第二位往前两位的和,第一位和第二位。
    • 第三位(3在二进制下=11 二进制下的1=1)的值为输入的数组的第三位往前一位的和,也就是第三位。
    • 第四位的值(4在二进制下=100 二进制下的100=4)的值为输入的数组的第四位往前四位的和,也就是第一位,第二位,第三位以及第四位。

    我们就称这种数组为树状数组

    代码

    1. void build(ll s,ll num){
    2. for(ll i=s;i<=n;i+=lowbit(i)) tree[i]+=num;//当s在i的范围内 第num位数组加上num
    3. }

    跟着代码走一遍:

    • 假设输入的n=5,输入的数为1 5 4 2 3(样例)
    • 输入第一个数时s=1,num=1。加上的树状数组数组位数为第一位,第二位,第四位。树状数组为:1 1 0 1 0
    • 输入第二个数时s=2,num=5,加上的位置为第二位,第四位。数组为1 6 0 6 0
    • 输入第三个数时s=3,num=4,加上的位置为第三位。数组为1 6 4 10 0
    • 输入第四个数时s=4,num=2,加上的位置为第四位。数组为1 6 4 12 0
    • 输入第五个数时s=5,num=3,加上的位置为第五位。数组为1 6 4 12 3

    至此,建树操作已经完成了。可以发现这个操作的本质就是修改树状数组的值,所以它也是单点修改的函数

    查询也是一样的。注意我们的操作不是区间求和,而是求两个前缀和的差!

    1. //反着的建树。此操作是求输入的数据第1到第s位的和。
    2. ll ask(ll s){
    3. ll ans=0;
    4. for(ll i=s;i>=1;i-=lowbit(i)) ans+=tree[i];//建树的反操作
    5. return ans;
    6. }

    举一个sample(example) 求第三项和第五项之间的和。 本质上就是求5的前缀和与2的前缀和的差。(1 2 3 4 5)-(1 2)=(3 4 5)

    • 先求5的前缀和。所求的就是第5(101)项+第4(100)项就是12+3=15。
    • 再求2的前缀和。所求的就是第2(10)项就是6。
    • 最后作差就是15-6=9。

    检验一下,3到5位的求和就是4+2+3=9。没有问题!

    把所有的结合起来就是树状数组啦~

    代码

    ACcode:

    1. #include<bits/stdc++.h>
    2. using namespace std;
    3. #define int long long
    4. const int N=5e5+10;
    5. int n,m,q,in[N],tr[N];
    6. int lowbit(int num) {
    7. return num&-num;//返回值为二进制下num从左往右第一个1的位置
    8. }
    9. void add(int s,int num) {
    10. for(int i=s; i<=n; i+=lowbit(i))tr[i]+=num;
    11. //当s在i的范围内,第num位数组加上num
    12. }
    13. int ask(int s) {
    14. int ans=0;
    15. for(int i=s; i>=1; i-=lowbit(i))ans+=tr[i];//寻找差分的标记
    16. return ans;
    17. }
    18. void solve() {
    19. cin>>n>>q;
    20. for(int i=1; i<=n; i++) cin>>in[i];
    21. while(q--) {
    22. int op;
    23. cin>>op;
    24. if(op==1) {
    25. int x,y,s;
    26. cin>>x>>y>>s;
    27. add(x,s);//差分
    28. add(y+1,-s);
    29. } else {
    30. int x;
    31. cin>>x;
    32. cout<<in[x]+ask(x)<<"\n";//区域查询则为右边界前缀和减去左边界前缀和
    33. }
    34. }
    35. }
    36. signed main() {
    37. ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    38. int t=1;
    39. // cin>>t;
    40. while(t--) {
    41. solve();
    42. }
    43. return 0;
    44. }

    over~

  • 相关阅读:
    李航老师《统计学习方法》第五章阅读笔记
    吐槽记~(这个帖子是我的垃圾桶)~哈哈
    IO流内容总结
    【开发随记】【提效】工作习惯那些事系列之五——任务处理
    【MVC 开发模式】
    巧用Stream流解决Page分页连表查询一对多展示错误的问题
    [附源码]Python计算机毕业设计Django基于web的羽毛球管理系统
    程序员命令行 · 脚本 cheatsheet
    年搜索量超 7 亿次背后:这款 APP 用火山引擎 DataTester 完成“数据驱动”
    图解 LeetCode 算法汇总——二分查找
  • 原文地址:https://blog.csdn.net/m0_74969835/article/details/133654594