树状数组系列题目总结
树状数组详解

第01讲什么是树状数组?树状数组用来求区间元素和,求一次区间元素和的时间效率为O(logn)。
有些同学会觉得很奇怪。
用一个数组S[i]保存序列A[]的前i个元素和,那么求区间i,j的元素和不就为S[j]-S[i-1],那么时间效率为O(1),岂不是更快?但是,如果题目的A[]会改变呢?例如:我们来定义下列问题:我们有n个盒子。
可能的操作为1.向盒子k添加石块2.查询从盒子i到盒子j总的石块数自然的解法带有对操作1为O(1)而对操作2为O(n)的时间复杂度。
但是用树状数组,对操作1和2的时间复杂度都为O(logn)。
第02讲图解树状数组C[]现在来说明下树状数组是什么东西?假设序列为A[1]~A[8]网络上面都有这个图,但是我将这个图做了2点改进。
(1)图中有一棵满二叉树,满二叉树的每一个结点对应A[]中的一个元素。
(2)C[i]为A[i]对应的那一列的最高的节点。
现在告诉你:序列C[]就是树状数组。
那么C[]如何求得?C[1]=A[1];C[2]=A[1]+A[2];C[3]=A[3];C[4]=A[1]+A[2]+A[3]+A[4];C[5]=A[5];C[6]=A[5]+A[6];C[7]=A[7];C[8]= A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8];以上只是枚举了所有的情况,那么推广到一般情况,得到一个C[i]的抽象定义:因为A[]中的每个元素对应满二叉树的每个叶子,所以我们干脆把A[]中的每个元素当成叶子,那么:C[i]=C[i]的所有叶子的和。
现在不得不引出关于二进制的一个规律:先仔细看下图:将十进制化成二进制,然后观察这些二进制数最右边1的位置:1 --> 000000012 --> 000000103 --> 000000114 --> 000001005 --> 000001016 --> 000001107 --> 000001118 --> 000010001的位置其实从我画的满二叉树中就可以看出来。
线段树以及数组数组类型各题总结-acm

线段树之hdu4027Can you answer these queries?Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65768/65768 K (Java/Others)Total Submission(s): 4014 Accepted Submission(s): 979Problem DescriptionA lot of battleships of evil are arranged in a line before the battle. Our commander decides to use our secret weapon to eliminate the battleships. Each of the battleships can be marked a value of endurance. For every attack of our secret weapon, it could decrease the endurance of a consecutive part of battleships by make their endurance to the square root of it original value of endurance. During the series of attack of our secret weapon, the commander wants to evaluate the effect of the weapon, so he asks you for help.Y ou are asked to answer the queries that the sum of the endurance of a consecutive part of the battleship line.Notice that the square root operation should be rounded down to integer.InputThe input contains several test cases, terminated by EOF.For each test case, the first line contains a single integer N, denoting there are N battleships of evil in a line. (1 <= N <= 100000)The second line contains N integers Ei, indicating the endurance value of each battleship from the beginning of the line to the end. Y ou can assume that the sum of all endurance value is less than 263.The next line contains an integer M, denoting the number of actions and queries. (1 <= M <= 100000)For the following M lines, each line contains three integers T, X and Y. The T=0 denoting the action of the secret weapon, which will decrease the endurance value of the battleships between the X-th and Y-th battleship, inclusive. The T=1 denoting the query of the commander which ask for the sum of the endurance value of the battleship between X-th and Y-th, inclusive.OutputFor each test case, print the case number at the first line. Then print one line for each query. And remember follow a blank line after each test case.Sample Input101 2 3 4 5 6 7 8 9 1050 1 101 1 101 1 50 5 81 4 8Sample OutputCase #1:1976Source/*一个10^5的序列,有10^5个操作,每个操作为a,b,ca=0时将b到c区间内的数都开根号下取整,a=1时求b到c段的和其中所有数的和不超过2^63。
树状数组

2 区间更新,单点求和
举个具体的栗子。。。
这道题目就是
意思挺明显的。。。 就是更改,查询。。。。
再举个特别的栗子。。。
求逆序数~
例如在序列 { 2, 4, 3, 1 } 中,逆序依次为 (2,1), (4,3), (4,1), (3,1),因此该序列的逆序数为 4。
所以如何求一个数组中逆序数的个数呢。。
//而且是用树状数组的方法。。。
例如 1 5 2 4 3
从输入顺序考虑: 输入1 ,无逆序;
输入 5, 无逆序;
输入 2,有逆序,因为前面有个5,有一个逆序对,这个逆序 对也可以这样考虑,此刻2所在的位置为3,而此刻小于等于2 的数有2 个,所以3-2=1; 输入 4,有逆序,同上,4-3=1; 输入 3 ,有逆序,同上,5-3=2; 所以~ ans=1+1+2~~~
3 树状数组的两类操作 1 单点更新,区间求和 1 一维树状数组,单点更新,区间求和 比如要更新点x ,x点的值加上val即调用add(x , val) , 求区间[1 , x]的和即为getSum(x) int lowbit(int x) { return x&(-x); } int getSum(int x){ int sum = 0; while(x){ sum += treeNum[x]; x -= lowbit(x); } return sum; } void add(int x , int val){ while(x <=N){ treeNum[x] += val; x += lowbit(x); } }
问题的提出
有一个一维数组,长度为n。
树状数组

树状数组武钢三中 吴豪【引言】在解题过程中,我们有时需要维护一个数组的前缀和S[i]=A[1]+A[2]+...+A[i]。
但是不难发现,如果我们修改了任意一个A[i],S[i]、S[i+1]...S[n]都会发生变化。
可以说,每次修改A[i]后,调整前缀和S[]在最坏情况下会需要O(n)的时间。
当n非常大时,程序会运行得非常缓慢。
因此,这里我们引入“树状数组”,它的修改与求和都是O(logn)的,效率非常高。
【理论】为了对树状数组有个形 象的认识,我们先看下面这张图。
如图所示,红色矩形表示的数组C[]就是树状数组。
这里,C[i]表示A[i-2^k+1]到A[i]的和,而k则是i在二进制时末尾0的个数,或者说是i用2的幂方和表示时的最小指数。
( 当然,利用位运算,我们可以直接计算出2^k=i&(i^(i-1)) )同时,我们也不难发现,这个k就是该节点在树中的高度,因而这个树的高度不会超过logn。
所以,当我们修改A[i]的值时,可以从C[i]往根节点一路上溯,调整这条路上的所有C[]即可,这个操作的复杂度在最坏情况下就是树的高度即O(logn)。
另外,对于求数列的前n项和,只需找到n以前的所有最大子树,把其根节点的C加起来即可。
不难发现,这些子树的数目是n在二进制时1的个数,或者说是把n展开成2的幂方和时的项数,因此,求和操作的复杂度也是O(logn)。
接着,我们考察这两种操作下标变化的规律:首先看修改操作:已知下标i,求其父节点的下标。
我们可以考虑对树从逻辑上转化:如图,我们将子树向右对称翻折,虚拟出一些空白结点(图中白色),将原树转化成完全二叉树。
有图可知,对于节点i,其父节点的下标与翻折出的空白节点下标相同。
因而父节点下标 p=i+2^k (2^k是i用2的幂方和展开式中的最小幂,即i为根节点子树的规模)即 p = i + i&(i^(i-1)) 。
接着对于求和操作:因为每棵子树覆盖的范围都是2的幂,所以我们要求子树i的前一棵树,只需让i减去2的最小幂即可。
树常见题目总结

树常见题⽬总结1.树的前序遍历递归:class Solution {public:vector<int> preorderTraversal(TreeNode* root) {vector<int> ans;preorder(ans,root);return ans;}void preorder(vector<int> &ans,TreeNode* root){if(root == NULL)return;ans.push_back(root->val);preorder(ans,root->left);preorder(ans,root->right);}};⾮递归:class Solution {public:vector<int> preorderTraversal(TreeNode* root){vector<int> res;stack<TreeNode*> tmp;if(root != NULL){tmp.push(root);}while(!tmp.empty()){res.push_back(tmp.top() -> val);root = tmp.top();tmp.pop();if(root -> right != NULL){tmp.push(root -> right);}if(root -> left != NULL){tmp.push(root -> left);}}return res;}};2.中序遍历递归:class Solution {public:vector<int>inorderTraversal(TreeNode* root){vector<int>res;inorder(root,res);return res;}void inorder(TreeNode* root,vector<int>& res){if(root == NULL){return;}inorder(root -> left,res);res.push_back(root -> val);inorder(root -> right,res);}};⾮递归:class Solution {public:vector<int> inorderTraversal(TreeNode* root) { vector<int> res;stack<TreeNode*>tmp;while(root != NULL || !tmp.empty()){if(root != NULL){tmp.push(root);root = root -> left;}else{root = tmp.top();res.push_back(root -> val);tmp.pop();root = root -> right;}}return res;}};3.后序遍历递归:class Solution {public:vector<int> postorderTraversal(TreeNode* root){ vector<int>res;postorder(res,root);return res;}void postorder(vector<int> &res,TreeNode* root){ if(root == NULL){return;}postorder(res,root -> left);postorder(res,root -> right);res.push_back(root -> val);}};⾮递归:class Solution {public:vector<int> postorderTraversal(TreeNode* root) { vector<int> res;stack<TreeNode*> tmp1;stack<TreeNode*> tmp2;if(root != NULL){tmp1.push(root);while(!tmp1.empty()){root = tmp1.top();tmp2.push(tmp1.top());tmp1.pop();if(root -> left != NULL)tmp1.push(root -> left);if(root -> right != NULL)tmp2.push(root -> right);}}while(!tmp2.empty()){res.push_back(tmp2.top() -> val);tmp2.pop();}return res;}};4.重建⼆叉树题⽬描述:输⼊某的前序遍历和中序遍历的结果,请重建出该⼆叉树。
数据结构树形结构章节练习含答案

数据结构…树形结构章节练习一.单项选择题1,如图所示的4棵二叉树中,_c_不是完全二叉树。
(A) (B) (C) (D)2.如图所示的4棵二叉树,_b_是平衡二叉树。
(A) (B) (C) (D)在线索化二叉树中,t所指结点没有左子树的充要条件是_b—。
A) t->left=NULL B) t->ltag= 1C) t->Itag=l且t->lcft=NULL D)以上都有不对二叉树按某种顺序线索化后,任一结点均有指向其前驱和后继的线索,这种说法_b—oA)正确B)错误二叉树的前序遍历序列中,任意一个结点均处在其子女结点的前而,这种说法_a—°A)正确B)错误由于二叉树中每个结点的度最大为2,所以二叉树是一种特殊的树,这种说法_b—o A)正确B)错误设高度为h的二叉树上只有度为0和度为2的结点,则此类二叉树中所包含的结点数至少为_b—。
A) 2hB)2h-l C) 2h+l D)h+1如图所示二叉树的中序遍历序列是_b —。
A) abcdgef B) dfebage C) dbaefcg D) defbagc9•已知某二叉树的后序適历序列是dabec,中序遍历序列是debac,它的前序遍历序列是_d—°A) acbed B) decab C) deabc D) cedba如果T2是由有序树T转换而来的二叉树,那么T中结点的前序就是T2中结点的—。
A)前序B)中序C)后序D)层次序如果T2是由有序树T转换而来的二叉结,那么T中结点的后序就是T2中结点的_b—。
A)前序B )中序C)后序D)层次序某二叉树的前序遍历结点访问顺序是abdgcefh, 中序遍历的结点访问顺序是dgbaechf,则其后序遍历的结点访问顺序是_d _____________________ 。
A) bdgcefha B) gdbecflia C) bdgaechf D) gdbehfca二叉树为二叉排序树的充分必要条件是其任一结点的值均大于英左孩子的值、小于其右孩子的值。
树状数组

树状数组是一个查询和修改复杂度都为log(n)的数据结构,假设数组a[1..n],那么查询a[1]+...+a[n]的时间是log级别的,而且是一个在线的数据结构,支持随时修改某个元素的值,复杂度也为log级别。
来观察这个图:令这棵树的结点编号为C1,。
令每个结点的值为这棵树的值的总和,那么容易发现:C1 = A1C2 = A1 + A2C3 = A3 C4 = A1 + A2 + A3 + A4C5 = A5C6 = A5 + A6C7 = A7C8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8...C16 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8 + A9 + A10 + A11 + A12 + A13 + A14 + A15 + A16这里有一个有趣的性质:设节点编号为x,那么这个节点管辖的区间为2^k(其中k为x二进制末尾0的个数)个元素。
因为这个区间最后一个元素必然为Ax,所以很明显:Cn = A(n –2^k + 1) + ... + An算这个2^k有一个快捷的办法,定义一个函数如下即可:int lowbit(int x){return x&(x^(x–1));}当想要查询一个SUM(n)(求a[n]的和),可以依据如下算法即可:step1:令sum = 0,转第二步;step2:假如n <= 0,算法结束,返回sum值,否则sum = sum + Cn,转第三步;step3: 令n = n –lowbit(n),转第二步。
可以看出,这个算法就是将这一个个区间的和全部加起来,为什么是效率是log(n)的呢?以下给出证明:n = n –lowbit(n)这一步实际上等价于将n的二进制的最后一个1减去。
而n的二进制里最多有log(n)个1,所以查询效率是log(n)的。
那么修改呢,修改一个节点,必须修改其所有祖先,最坏情况下为修改第一个元素,最多有log(n)的祖先。
树状数组

树 状
围内的数据之和.
修改一个位置上数字的值,就是修改一个 叶子结点的值,而当程序由叶子结点返回
数 组
根节点的同时顺便修改掉路径上的结点的 a数组的值.
对于询问的回答,可以直接查找i..j范围
的 运
内的值,遇到分叉时就兵分两路,最后在合 起来.也可以先找出0..i-1的值和0..j的 值,两个值减一减就行了.后者的实际操作
树
每个星星有个坐标。 如果一个星星的左下方(包含正左和正下)有k颗
状
星星,就说这颗星星是k级的.比如,在下面的例
数
图中,星星5是3级的(1,2,4在它左下)。 星星2,4是1级的。例图中有1个0级,2个1级,1
组
个2级,1个3级的星。
的
运
用
求出各个级别的星星的个数
树 状
算法有很多种,最实用的是树状数 组
状
如果直接做的话,修改的复杂度
数
是O(1),询问的复杂度是O(N),M
组
次询问的复杂度是M*N.M,N的
的
范围可以有100000以上
运
用
用线段树可以这样解:树源自若要维护的序列范围是0..5,先构造下面
状
的一棵线段树:
数
组
的
运
用
可以看出,这棵树的构造用二分便可以实 现.复杂度是2*N.
每个结点用数组a来表示该结点所表示范
的
while k<=n do
运
begin
用
C[k]:=C[k]+delta; k:=k+lowbit(k);
End;
End;
若要求I..j的元素和的值,则求出
1..I-1的值和1..j的值.
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
M n 2i n lg n (前面两种均为Θ(n) ) ,而且在实际编程中,线段树通常是使
i 0
lg n
用指针进行实现的, 这样的递归和调用就为我们的时间复杂度填上了一个不小的常数 (虽然 在实际分析中常常省去常数的大小,但是在此处它的确是一个不可忽略的因素) 。更要命的 是,线段树的编程难度比较大,稍一疏忽就容易写错,也会消耗很多编程的时间。 在分析了几种不是很乐观的方案之后,沮丧之余,树状数组的出现就像救世主一样拯 救了我们的思路。它可以在Θ(lg n) 的时间内完成一次查询或是修改操作,且只占用Θ(n) 大小的空间(也就是一个数组) ,而且它的代码十分短小精悍,只要牢记它的基本思想就可 以在很短的时间内写出准确无误的代码。
Input
The first line of the input file contains a number of stars N (1<=N<=15000). The following N lines describe coordinates of stars (two integers X and Y per line separated by a space, 0<=X,Y<=32000). There can be only one star at one point of the plane. Stars are listed in ascending order of Y coordinate. Stars with equal Y coordinates are listed in ascending order of X coordinate.
-1-
树状数组系列题目总结 南开中学 莫凡
S16
S8
S4 S2 S1 S3 S5 S6 S7 S9 S10 S11
S12 S14 S13 S15
A1
A2
A3
A4
A5
A6
A7
A8
A9
A10 A11
A12
A13
A14
A15
A16
关于这个图应该多说几句,否则不是很好理解。我们用 A 来表示原来给定的数组,S 表示我们构造出的树状数组。图中两点之间的连线表示一种相加关系,根据这张图,我们是 这样定义这个 S 数组的:S[n]的值等于所有在 S 的下方且与 S 有连线的点的值的和。例如, S[1]=A[1], S[2]=S[1]+A[2], 以及 S[12]=S[10]+S[11]+A[12]等等。 字面上 S 数组的准确定义并 不是那么容易描述, 但是这个图为我们呈现了一种显而易见却又难以描述的规律 (使自己明 白这种规律的最好方法就是照猫画虎重新画一个 32 节点的树) 。 我们现在要做的是仔细研究 分析出它的规律和定义方式,然后对它的效率进行简单的分析。 一步登天总是妄想,对一件事情着手分析时,应该尽可能从最简单的情况入手(但有 时候情况过于简单,什么结论都无法得到) 。假设现在,我们需要将 A[6]到 A[14]的和用 S 数组来表示,我们可以得到这样的结果:sum = S[14] + S[12] + S[8] - S[4] - S[5]。这时候可 以回过头去看一眼上一节所说的方案二, sum = B[14] – B[5]。 很显然的, B[14] = S[14] + S[12] + S[8], B[5] = S[5] + S[4]。在查询的时候我们依旧采用方案二中的思路,就将这个问题转化 成了使用 S 数组来表示 B 数组的问题了,由于符号单一而且更容易发现规律,我们很快可 以得出这件事(接下来的伪代码中我们尝试使用 S 数组来表示 B[n]): ans←0; while(n > 0) begin ans←ans + S[n]; if(n 是 2^x 的倍数而不是 2^(x + 1)的倍数) //x 是一个唯一符合要求的自然数 then n←n-2^x; end; 现在问题的关键转变为了如何去计算这个 x,或是 2^x,潜意识中 2^x 总是和 x 位二进 制有关系——2^x 在二进制中是 1 后面加上 x 个 0。 我们需要了解一个显而易见的引理: 引理 1 一个数被 2^x 整除的充要条件是它的二进制形式的后 x 位均为 0 由于 n 是 2^x 的倍数,所以 n 的后 x 位一定都是 0,而且 n 不是 2^(x+1)的倍数,所以 n 的倒数第 x 位一定为 1,也就是说,我们只需要找出 x 的二进制中最后一个是 1 的位连同它 之后的 0 构成一个新的二进制值, 我们将其记做 lowbit(n) (我们将在下一节中讨论它的计算 方法) 。这样一来,计算 B[n]的详细代码就可以写出(code in C++):
Sample Input
5 1 1 5 1
-4-
树状数组系列题目总结 南开中学 莫凡
7 1 3 3 5 5
Sample Output
1 2 1 1 0
B 题意分析
题目中给出一些(n 个)星星的坐标,将每颗星星的等级定义为纵坐标小于等于它且横 坐标小于等于它(不包括自己)的星星数,而且不会出现两颗星星坐标相同的情况,要求统 计出每一等级(0 ~ n-1)的星星总数。特别地,输入数据按照纵坐标的升序排序,在纵坐标 相等时按照横坐标的升序排序给出
-2-
树状数组系列题目总结 南开中学 莫凡
int B(int n) { int ans = 0; while(n > 0){ ans += S[n]; n -= lowbit(n); } return ans; } 当然,像下面这样写会更简洁: ans = 0; for(int i = n; i > 0; i -= lowbit(i)) ans += S[i]; 通过延续方法二的思路使查询变得简单起来,通过使用 S 数组现场计算 B 数组可以缩 短修改 B 数组的时间。修改的操作和查询几乎如出一辙,通过观察,只需要进行以下操作 即可完成修改: void change(int x, int V) //x 是要修改的位置,V 是改变量 { for(int i = 1; i <= Array_size; i +=lowbit(i)) S[i] += V }
树状数组系列题目总结 南开中学 莫凡
树状数组系列题目总结
第一部分 树状数组数据结构浅析
一、我们面对的问题
假设有这样一个问题:现在我们有一个给定的初始数列 A,它支持两种操作:修改和查 询。修改操作是这样的:在第 a 个数上加上 b(当然,也有可能是减去) 。查询操作要求输 出第 i 个数到第 j 个数之间的总和。 如果直接去处理一个数组的话就可以在Θ(1)的时间内完成一次修改操作,但是它需 要话费Ο(n)的时间完成一次查询操作,这样的效率非常的低,在查询操作比较多而且查 询的区间比较长的话会花费极大的时间。 在统计一段数字的和的时候我们会想到使用一个数组 B[n]来存储 1~n 的部分和,然后 查询时只需要输出 B[j] – B[i – 1]的值即可,这样的优化就将查询的时间减小到了Θ(1) ,但 是很不幸,在修改第 i 个数字的值的时候,B[i]及之后的每一个部分和都需要被修改,这样 就使得每一次修改的时间变为了Ο(n) ,仍然不是高效的方法。 到这时我们不由得想到处理连续区间上问题的必杀神器线段树了,当然,线段树可以 有效地平衡查找和修改的时间消耗,使得问题得以解决,但是线段树的空间复杂度比较大
For example, look at the map shown on the figure above. Level of the star number 5 is equal to 3 (it's formed by three stars with a numbers 1, 2 and 4). And the levels of the stars numbered by 2 and 4 are 1. At this map there are only one star of the level 0, two stars of the level 1, one star of the level 2, and one star of the level 3. You are to write a program that will count the amounts of the stars of each level on a given map.
C 算法分析
这题属于树状数组的基础题。 根据观察题目中给出的输入数据的性质就可以发现, 在每 颗星星的坐标被输入时,它的等级已经确定(所有满足条件的星星都已经在它之前给出) 。 而且它保证了之前的每一颗星星都满足纵坐标的条件, 所以我们只需要统计当前横坐标小于 等于 Xi 的星星个数就可以计算出 Stari 的等级了,所以通过使用一个树状数组统计每个横坐 标位置上有多少颗星星即可了。 需要注意的是为了使树状数组的下标从一开始, 我们在读入 之后应该将横坐标+1 之后在进行统计。
Output
The output should contain N lines, one number per line. The first line contains amount of stars of the level 0, the second does amount of stars of the level 1 and so on, the last line contains amount of stars of the level N-1.
三、巧妙的 lowbit 运算
lowbit 可谓是树状数组这个数据结构的精华思想所在,它决定了树状数组的定义、修改 和查询。可想而知,lowbit 的运算应该是在常数时间内完成的,否则树状数组的时间效率绝 不可能如此之高。很显然,既然是取最后一位,工作自然是通过位运算完成的。在此,我们 不再去探究它的探索过程,而是直接给出结论在对其进行说明。 lowbit(x) = x and (-x) 由于计算机中使用补码的存储方式,-x 的值实际上是 x 按位取反再+1。首先抛开位数 的限制我们要明白一个问题,那就是 10000……(x 个 0)的取反是 01111……(x 个 1) ,再 加一结果依然是 10000……(x 个 0)而无论倒数第一个 1 的左侧是什么,在取反之后都不 会受到+1 的影响,也就是说,x 与-x 在补码的存储方式中有且仅有倒数第 x+1(x 为右侧 0 的个数)位同为 1,用它们进行按位与运算即可得出 lowbit 的正确结果。