树状数组解决的问题:

假如有这样一种情景,先输入一个长度为n的数组,然后我们有如下两种操作:

  1. 输入一个数m,输出数组中下标1~m的前缀和
  2. 对某个指定下标的数进行值的修改

多次执行上述两种操作;

常规方法

对于一个的数组,如果需要求1~m的前缀和我们可以将其从下标1开始对m个数进行求和,对于值的修改,我们可以直接通过下标找到要修改的数,然后更新前缀和,对于一次操作显然没什么问题,但对于nnn次操作,时间复杂度就达到了O(n2)O(n^2)O(n2)和O(n)O(n)O(n),这样的方法就显得不适用了。

树状数组


如图,对于一个长度为n的数组,A数组存放的是数组的初始值,引入一个辅助数组C;

C1 = A1
C2 = C1 + A2 = A1 + A2
C3 = A3
C4 = C2 + C3 + A4 = A1 + A2 + A3 + A4
C5 = A5
C6 = C5 + A6 = A5 + A6
C7 = A7
C8 = C4 + C6 + C7 + A8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8

我们称C[i]的值为下标为i的数所管辖的数的和,下标为i的数所管辖的元素的个数为2k2^k2k个(ki的二进制的末尾0的个数),例如:

  • i = 8 = 1000,末尾3个0,故k = 3,所管辖的个数为23=82^3 = 823=8,C8是8个数的和;
  • i = 5 = 0101,末尾没有0,故k = 0,所管辖的个数为20=12^0 = 120=1,C5是一个数的和;

而对于输入的数m,我们要求编号为m的数的前缀和A1⋯AmA_1\cdots A_mA1​⋯Am​,按照上面说的,summ=Ci1+Ci2+⋯sum_m= C_{i_1} + C_{i_2} + \cdotssumm​=Ci1​​+Ci2​​+⋯,这里mC[i]的对应关系是这样的,对于查询的m,将它转换成二进制后,不断对末尾的1的位置进行-1的操作,直到全部为0停止,中间得到的值就是c[i],例如:

  • m = 7,sum7=C7+C6+C4sum_7 = C_7 + C_6 + C_4sum7​=C7​+C6​+C4​,7的二进制为0111(C7C_7C7​得到),对0111的末尾1的位置-1,得到0110 = 6(C6C_6C6​得到),再对0110末尾1位置-1,得到0100 = 4(C4C_4C4​得到),最后对0100末尾1位置-1后得到0000(结束),计算停止,至此C7,C6,C4C_7,C_6,C_4C7​,C6​,C4​全部得到,求和后就是m = 7时它的前缀和;
  • m = 6,sum6=C6+C4sum_6 = C_6 + C_4sum6​=C6​+C4​,6的2进制等于·0110·,经过两次变换后为0100(C4)和0000(结束信号),那么求和后同样也得到了预计的结果;

那么求前缀和的代码如下:

int lowbit(int m){return m & (-m);
}int getSum(int m){int ans = 0;while(m > 0){ans += C[m];m -= lowbit(m);}return ans;
}

关于m & (-m)这是一个很巧妙的地方,如13的二进制表示为1101,那么-13的二进制表示为0010 + 0001 = 0011,那么1101 & 0011 = 0001,二进制末尾1的位置是202^020,将13 - 0001 = 12,再对12执行lowbit操作,1100 & 0100 = 0100,二进制末尾1的位置是222^222,将12 - 0100 = 8,再对8执行lowbit操作,0100 & 1100 = 0100,二进制位是222^222,8 - 0100 = 0(结束操作),通过循环得到的13,12,8,则sum13=C13+C12+C8sum_{13} = C_{13} + C_{12} + C_8sum13​=C13​+C12​+C8​.

建立树状数组

对于一个输入的数组A,我们一次读取的过程,就可以想成是一个不断更新值的过程,所以建树与单点更新值是一样的,即把A1⋯AnA_1\cdots A_nA1​⋯An​从0更新成我们输入的A[i],所以一边读入A[i],一边将C[i]涉及到的祖先节点值更新,完成输入后树状数组C也就建立成功了。

下面说说如何更新节点值:

假设更新A[2] = 5,通过观察我们得知,如果修改了A[2]的值,那么管辖A[2]C[2],C[4],C[8]的前缀和都要加上5(所有的祖先节点),那么和查询类似,我们如何得到C2的所有祖先节点呢,依旧是上述的巧妙的方法,但是我们把它倒过来用,对于要更新i位置的值,我们把i转换成二进制,不断对二进制最后一个1的位置+1,直到达到数组下标的最大值n结束,对于给出的例子i = 2,假设数组下标上限n = 8i转换成二进制后等于0010(C2C_2C2​),对末尾1的位置进行+1,得到0100(C4C_4C4​),对末尾的1的位置进行+1,得到1000(C8C_8C8​),循环结束,对C2,C4,C8C_2,C_4,C_8C2​,C4​,C8​的前缀和都要+5,当然不能忘记对A[2]的值+5,单点更新值过程结束。

void update(int x, int value){A[x] += value;   // 修改源数组while(x <= n){C[x] += value;x += lowbit(x);}
}

完整代码

#include<stdio.h>
#include<string.h>int a[10005];
int c[10005];
int n;int lowbit(int x){return x&(-x);
}int getSum(int x){int ans = 0;while(x > 0){ans += c[x];x -= lowbit(x);}return ans;
}void update(int x, int value){a[x] += value;while(x <= n){c[x] += value;x += lowbit(x);}
}int main(){while(scanf("%d", &n)!=EOF){memset(a, 0, sizeof(a));memset(c, 0, sizeof(c));for(int i = 1; i <= n; i++){scanf("%d", &a[i]);update(i, a[i]);}int ans = getSum(n-1);printf("%d\n", ans);}   return 0;
}

参考:https://www.cnblogs.com/findview/archive/2019/08/01/11281628.html

树状数组(简单介绍)相关推荐

  1. CF56E Domino Principle 树状数组 + 简单dp

    一个比较简单的题,但是我还是没做出来(哭. 很容易想到从后往前做,所以我们可以维护一个dp数组f,f(i)表示到第i个牌倒下能达到的最远距离. f直接倒着跑,每次取[x,x+h−1][x,x+h-1] ...

  2. 树状数组(树状数组的基本用法与操作)

    什么是树状数组?树状数组简单的来说就是将一个数组模拟树形结构. 树状数组有什么用?树状数组可以将求和的操作从O(n)操作简化为O(logn). 如图所示,横线下方为a数组表示为初试数据:上方为数组c, ...

  3. P2161 [SHOI2009]会场预约[线段树/树状数组+二分/STL]

    题目描述 PP大厦有一间空的礼堂,可以为企业或者单位提供会议场地.这些会议中的大多数都需要连续几天的时间(个别的可能只需要一天),不过场地只有一个,所以不同的会议的时间申请不能够冲突.也就是说,前一个 ...

  4. bzoj2683:简单题(树状数组套CDQ分分治)

    CDQ(陈丹琦)分治 CDQ显然是一个人的名字(2008NOI金牌选手陈丹琦) 这种离线的分治算法在算法界被称为"CDQ分治".  首先回忆一下归并排序的分治, 它的操作是将数组二 ...

  5. 简单の暑假总结——树状数组

    2.1 树状数组 树状数组,顾名思义,长得像树的数组,用于处理一些单点修改以及区间查询的问题.其时间复杂度为 O(log⁡2n)O(\log _2n)O(log2​n) .如过我们使用一些一般的数据结 ...

  6. 树状数组维护区间和的模型及其拓广的简单总结

    by wyl8899 树状数组的基本知识已经被讲到烂了,我就不多说了,下面直接给出基本操作的代码. 假定原数组为a[1..n],树状数组b[1..n],考虑灵活性的需要,代码使用int *a传数组. ...

  7. 7.25 树状数组的简单应用题

    首先我们都了解过树状数组这一短小精悍的利器,知道它和差分以及推公式的简单结合.但很显然问题不会那么裸. 下面我们将讨论两道关于树状数组的简单应用题,看看具体包装下的树状数组题目 一.   楼兰图腾  ...

  8. 洛谷 P5057 [CQOI2006]简单题(树状数组)

    嗯... 题目链接:https://www.luogu.org/problem/P5057 首先发现这道题中只有0和1,所以肯定与二进制有关.然后发现这道题需要支持区间更改和单点查询操作,所以首先想到 ...

  9. HDU 2836 Traversal 简单DP + 树状数组

    题意:给你一个序列,问相邻两数高度差绝对值小于等于H的子序列有多少个. dp[i]表示以i为结尾的子序列有多少,易知状态转移方程为:dp[i] = sum( dp[j] ) + 1;( abs( he ...

最新文章

  1. 支持向量机SVM序列最小优化算法SMO
  2. Spring Cloud第八篇:Spring Cloud Bus刷新配置
  3. 初次安装Magento商城 后台报错的解决方案
  4. 子元素是字典列表转成字典
  5. 针对深度学习的GPU芯片选择
  6. Spring的核心机制依赖注入,Junit测试与Java基础Getter和Setter两种方法意义——2017.07.26...
  7. tomcat配置文件context.xml和server.xml分析
  8. 马斯克:特斯拉智能召唤功能已被使用超过55万次
  9. 西安理工大学计算机考研备考指南(863数据结构)
  10. java语句以什么结尾_[JAVA] 关于语句的结尾
  11. 通用数据库连接工具--DbVisualizer的使用
  12. WIFI测试APP(华为、华三、锐捷)
  13. CBR,VBR,ABR,CQP四种编码方式。
  14. worldpress自定义页面
  15. jquery鼠标经过水平180度翻转效果
  16. Dreamweaver CS6 Mac破解版
  17. 正大国际期货:什么是黄金保证金交易?黄金保证金交易包含哪些要点?
  18. 【Matlab】帮助文档打不开
  19. 【TensorFlow】官方例子mnist_with_summaries.py在windows下运行tensorboard
  20. 系统定时任务与延时任务

热门文章

  1. 华为鸿蒙开发者导师,“云”上视听 | 带你玩转华为鸿蒙科技Harmony OS
  2. 学习笔记(4):Python语言家族-Python函数参数-默认参数、可变参数
  3. MySQL 添加索引,删除索引及其用法
  4. HarmonyOS玩转ArkUI动效 - 水母动画
  5. 小程序使用threejs第三篇—介绍几种几何模型
  6. linux 操作命令汇总
  7. Memcached prepend 命令的介绍及其使用实例
  8. 错误0x800703e3服务器传输文件,windows 大文件共享传输错误 0X8007003B
  9. path.resolve() 通俗解释、实例
  10. 3. 第一个 Java 程序 - Hello World【连载 3】