树状数组
第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 --> 00000001
2 --> 00000010
3 --> 00000011
4 --> 00000100
5 --> 00000101
6 --> 00000110
7 --> 00000111
8 --> 00001000
1的位置其实从我画的满二叉树中就可以看出来。但是这与C[]有什么关系呢?接下来的这部分内容很重要:在满二叉树中,
以1结尾的那些结点(C[1],C[3],C[5],C[7]),其叶子数有1个,所以这些结点C[i]代表区间范围为1的元素和;
以10结尾的那些结点(C[2],C[6]),其叶子数为2个,所以这些结点C[i]代表区间范围为2的元素和;
以100结尾的那些结点(C[4]),其叶子数为4个,所以这些结点C[i]代表区间范围为4的元素和;
以1000结尾的那些结点(C[8]),其叶子数为8个,所以这些结点C[i]代表区间范围为8的元素和。
扩展到一般情况:
i的二进制中的从右往左数有连续的x个“0”,那么拥有2^x个叶子,为序列A[]中的第i-2^x+1到第i个元素的和。终于,我们得到了一个C[i]的具体定义:
C[i]=A[i-2^x+1]+…+A[i],其中x为i的二进制中的从右往左数有连续“0”的个数。
第03讲 利用树状数组求前i个元素的和S[i]
理解了C[i]后,前i个元素的和S[i]就很容易实现。从C[i]的定义出发:
C[i]=A[i-2^x+1]+…+A[i],其中x为i的二进制中的从右往左数有连续“0”的个数。
我们可以知道:C[i]是肯定包括A[i]的,那么:S[i]=C[i]+C[i-2^x]+…
也许上面这个公式太抽象了,因为有省略号,我们拿一个具体的实例来看:
S[7]=C[7]+C[6]+C[4]
因为C[7]=A[7],C[6]=A[6]+A[5],C[4]=A[4]+A[3]+A[2]+A[1],所以S[7]=C[7]+C[6]+C[4]
(1)i=7,求得x=0,那么我们求得了A[7];
(2)i=i-2^x=6,求得x=1,那么求得了A[6]+A[5];
(3)i=i-2^x=4,求得x=2,那么求得了A[4]+A[3]+A[2]+A[1]。
讲到这里其实有点难度,因为S[i]的求法,如果要讲清楚,那么得写太多的东西了。所以不理解的同学,再反复多看几遍。
从(1)(2)(3)这3步可以知道,求S[i]就是一个累加的过程,如果将2^x求出来了,那么这个过程用C++实现就没什么难度。现在直接告诉你结论:2^x=i&(-i)
证明:设A’为A的二进制反码,i的二进制表示成A1B,其中A不管,B为全0序列。那么-i=A’0B’+1。由于B为全0序列,那么B’就是全1序列,所以-i=A’1B,所以:i&(-i)= A1B& A’1B=1B,即2^x的值。
所以根据(1)(2)(3)的过程我们可以写出如下的函数:
int Sum(int i) //返回前i个元素和
{
int s=0;
while(i>0)
{
s+=C[i];
i-=i&(-i);
}
return s;
}
第04讲 更新C[]
正如第01讲提到的小石块问题,如果数组A[i]被更新了怎么办?那么如何改动C[]?如果改动C[]也需要O(n)的时间复杂度,那么树状数组就没有任何优势。所以树状数组在改动C[]上面的时间效率为O(logn),为什么呢?因为改动A[i]只需要改动部分的C[]。这一点从第02讲的图中就可以看出来:
如上图:假如A[3]=3,接着A[3]+=1,那么哪些C[]需要改变呢?
答案从图中就可以得出:C[3],C[4],C[8]。因为这些值和A[3]是有联系的,他们用树的关系描述就是:C[3],C[4],C[8]是A[3]的祖先。那么怎么知道那些C[]需要变化呢?我们来看“A”这个结点。这个“A”结点非常的重要,因为他体现了一个关系:A的叶子数为C[3]的2倍。因为“A”的左子树和右子树的叶子数是相同的。因为2^x代表的就是叶子数,所以C[3]的父亲是A,A的父亲是C[i+2^0],即C[3]改变,那么C[3+2^0]也改变。我们再来看看“B”这个结点。B结点的叶子数为2倍的C[6]的叶子数。所以B和C[6+2^1]在同一列,所以C[6]改变,C[6+2^1]也改变。
推广到一般情况就是:
如果A[i]发生改变,那么C[i]发生改变,C[i]的父亲C[i+2^x]也发生改变。这一行的迭代过程,我们可以写出当A[i]发生改变时,C[]的更新函数为:
void Update(int i,int value) //A[i]的改变值为value
{
while(i<=n)
{
C[i]+=value;
i+=i&(-i);
}
}
第05讲 一维树状数组的应用举例
废了4讲的话,我们终于把一维树状数组的2个不到5行的代码给搞定了。现在要正式投入到应用当中。
题目链接:http://poj.org/problem?id=2352
题意:按照y升序给你n个星星的坐标,如果有m个星星的x,y坐标均小于等于星星A的坐标,那么星星A的等级为m。
分析:是一道树状数组题。举例来说,以下是题目的输入:
5
1 1
5 1
7 1
3 3
5 5
由于y坐标是升序的且坐标不重复,所以在星星A后面输入的星星的x,y坐标不可能都小于等于星星A。假如当前输入的星星为(3,3),易得我们只需要去找树状数组中小于等于3的值就可以了,即GetSum(3)。注意:A[i]表示x坐标为i的个数,C[]为A[]的树状数组,那么GetSum(i)就是序列中前i个元素的和,即x小于等于i的星星数。
本题还是一点要注意:星星坐标的输入可以是(0,0),所以我们把x坐标统一加1,然后用树状数组实现。
第06讲 二维树状数组
BIT可用为二维数据结果。假设你有一个带有点的平面(有非负的坐标)。你有三个问题:
1.在(x , y)设置点
2.从(x , y)移除点
3.在矩形(0 , 0), (x , y)计算点数 - 其中(0 , 0)为左下角,(x , y)为右上角,而边是平行于x轴和y轴。
对于1操作,在(x,y)处设置点,即Update(x,y,1),那么这个Update要怎么写?很简单,因为x,y坐标是离散的,所以我们分别对x,y进行更新即可,函数如下:
void Update(int x,int y,int val)
{
while(x<=n)
{
int y1=y;
while(y1<=n)
{
C[x][y1]+=val;
y1+=y1&(-y1);
}
x+=x&(-x);
}
}
那么根据Update可以推得:GetSum函数为:
int GetSum(int x,int y)
{
int sum=0;
while(x>0)
{
int y1=y;
while(y1>0)
{
sum+=C[x][y1];
y1-=y1&(-y1);
}
x-=x&(-x);
}
return sum;
}
第07讲 二维树状数组的应用举例
题目链接:http://poj.org/problem?id=2155
我们先讨论POJ2155的一维情况,如下:
有一个n卡片的阵列。每个卡片倒放在桌面上。你有两个问题:
1. T i j (反转从索引i到索引j的卡片,包括第i张和第j张卡——面朝下的卡将朝上;面朝上的卡将朝下)
2. Q i (如果第i张卡面朝下回答0否则回答1)
解决:
解决问题(1和2)的方法有时间复杂度O(log n)。在数组f(长度n + 1)我们存储每个问题T(i, j)——我们设置f[i]++和f[j + 1]--。对在i和j之间(包括i和j)每个卡k求和f[1] + f[2] + ... + f[k]将递增1,其他全部和前面的一样(看图2.0清楚一些),我们的结果将描述为和(和累积频率一样)模2。
图 2.0
使用BIT来存储(增加/减少)频率并读取累积频率。
理解了一维的情况,POJ2155就是其二维的版本,易得只需要更(x1,y1),(x1,y2+1),(x2+1,y1),(x2+1,y2+1)四个点的C[]的值就可以了,最后的结果依然是GetSum(x,y)%2
POJ 2352的代码如下:
#include<iostream>
using namespace std;
const int NMAX = 15005;
const int MMAX = 32005;
int n, ar[MMAX], lev[NMAX];
int lowbit(int i)
{
return i&(-i);
}
void add(int i)
{
while(i <= MMAX)
{ // 这里不能用n,要区别开来。
ar[i] += 1;
i += lowbit(i);
}
}
int sum(int i)
{
int ans = 0;
while(i > 0)
{
ans += ar[i];
i -= lowbit(i);
}
return ans;
}
int main()
{
int i, x, y;
scanf("%d", &n);
for(i = 0; i < n; i ++)
{
scanf("%d%d", &x, &y);
lev[sum(++x)] ++; // 一定要++x,以后要注意下,WA在了这。
add(x);
}
for(i = 0; i < n; i ++)
printf("%d\n", lev[i]);
return 0;
}
以上文字内容与来之http://blog.sina.com.cn/s/blog_8627bf080100sq46.html
自己关于树状数组的学习也将继续。。。。
- 大小: 23 KB
- 大小: 17.9 KB
- 大小: 21.3 KB
- 大小: 17.5 KB
分享到:
相关推荐
### POJ 2352 Stars - 树状数组与线段树实现 #### 题目背景 POJ 2352 Stars 是一道经典的算法题目,它提供了使用树状数组或线段树来解决的可能性。这两种数据结构都是为了高效处理区间查询问题而设计的。在这篇...
### POJ 2352:星图级别分布问题解析 #### 问题描述 本问题主要涉及天文学家对星图的研究。在星图中,星星被表示为平面上的点,并且每个星星都有对应的笛卡尔坐标(X, Y)。定义一个星星的“级别”为在它左下方的...
* 静态二叉检索树:例如 poj2482、poj2352。 * 树状树组:例如 poj1195、poj3321。 * RMQ:例如 poj3264、poj3368。 * 并查集的高级应用:例如 poj1703、poj2492。 * KMP 算法:例如 poj1961、poj2406。 4. ...
- (poj2482, poj2352):将数据分为多个区块,提高查询效率。 3. **树套树**: - (poj1195, poj3321):将一种数据结构嵌套在另一种数据结构中。 4. **区间最大值查询**: - (poj3264, poj3368):如何快速获取...
poj2528、poj2777等是线段树的题目,poj2482、poj2352等涉及平衡树,poj1195、poj3321则训练了树状数组。 5. **搜索算法的优化**:poj1699、poj3411等训练搜索技巧和剪枝方法,poj3373、poj1691则介绍了记忆化搜索。...
- 推荐题目:[poj2482](https://vjudge.net/problem/POJ-2482)、[poj2352](https://vjudge.net/problem/POJ-2352) - 状态压缩DP用于处理状态空间较大的问题。 3. **后缀数组** - 推荐题目:[poj1195]...
2. **平衡树**:如AVL树、红黑树等(poj2482, poj2352)。 3. **并查集**:用于处理不相交集合的问题(poj1195, poj3321)。 4. **区间查询**:如RMQ问题(poj3264, poj3368)。 5. **字符串匹配**:如KMP算法(poj...
+ poj2482, poj2352 * 树状树组 + poj1195, poj3321 * RMQ + poj3264, poj3368 * 并查集的高级应用 + poj1703, poj2492 * KMP算法 + poj1961, poj2406 四、搜索 * 最优化剪枝和可行性剪枝 * 搜索的技巧和优化...
- 示例题目:poj2482, poj2352 - **并查集** - 示例题目:poj1195, poj3321 - **区间查询(RMQ)** - 示例题目:poj3264, poj3368 - **后缀数组** - 示例题目:poj1703, poj2492 - **KMP算法** - 示例题目...
- **示例题目**: POJ 3267, POJ 1836, POJ 1260, POJ 2533, POJ 3176, POJ 1080, POJ 1159 - **注意事项**: 理解每种模型的状态定义和转移方程。 #### 六、数学 **1. 组合数学** - **定义**: 研究组合结构的...
2. **平衡树**(如poj2482, poj2352):如AVL树、红黑树等,保持树的高度平衡,适用于动态数据集合的维护。 3. **差分约束系统**(如poj1195, poj3321):解决变量间的关系约束问题,适用于资源分配、时间表编排等...
1. POJ2352【基础】: 这道题目是关于星星坐标的等级计算,利用树状数组实现动态更新和区间求和。树状数组在这里的主要作用是快速统计某个位置左边和下方星星的数量,进而计算出每个星星的等级。对于每颗星星的坐标...
- **POJ 2352**:求解x坐标上已有的星星数量。这通常需要使用线段树来快速查询和更新区间内的元素计数。 - **HDU 1255**:求解矩形面积的交集。在更新过程中需要特别注意如何正确地进行区间更新操作。 - **POJ ...
- **树上前缀和**:类似于数组的前缀和,但在树上可以用于求解区间和,常与LCA结合解决区间查询问题,如POJ2352_Stars等题目。 - **树上差分**:是对树上前缀和的一种推广,可以用于求解区间加减操作后的区间和,...
第十一类是线段树,涵盖了至少两个题目,包括 2352 和 2528 等题目。 第十二类是计算几何,涵盖了至少两个题目,包括 1113 和 1922 等题目。 第十三类是高精度,涵盖了至少三个题目,包括 1001 和 1413 等题目。 ...
* 短代码:1147、1163、1922、2211、2215、2229、2232、2234、2242、2245、2262、2301、2309、2313、2334、2346、2348、2350、2352、2381、2405、2406 * 中短代码:1014、1281、1618、1928、1961、2054、2082、2085...
- **2352** 和 **2528** 两题适合了解线段树的基本应用,虽然没有最少题数要求,但练习线段树对于提高算法能力非常有帮助。 ### 第十二类:计算几何 #### 重要性: 计算几何在图形处理、地图绘制等领域有着广泛的...
1129 1149 1154 1165 1182 1185 1190 1191 1201 1251 1273 1275 1276 1286 1322 1338 1363 1364 1401 1456 1459 1564 1579 1637 1657 1658 1679 ...2104 2112 2115 2186 2255 2352 2369 2406 2409 2421 2479 2480 2498
1001 1002 1003 1004 1005 1006 1007 1008 1011 1012 1013 1014 1015 1017 ...2352 2356 2362 2363 2377 2385 2386 2388 2392 2395 2406 2411 2418 2421 2441 2479 2485 2487 2488 2506 2513 2521 2524 2533 2538 2545 ...
1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1011 1012 1013 1014 ...2352 2353 2362 2371 2378 2386 2387 2388 2389 2392 2393 2394 2402 2403 2406 2411 2413 2419 2421 2446 2449 2456 2479 2488 2492 2503 ...