这几天学习了树状数组。记录一些心得体会。
对于一个普通数组a[n],查询某一个元素的时间复杂度是O(1),对数组的某一部分求和的时间复杂度是O(n)。当题目要求大量计算区间和时,用普通方法很有可能超时。而树状数组作为一种数据结构,英文名binary index tree(二进制索引树)。从它的英文名字就可以有一个大体的了解,这种数据结构和“2”有关。其查询和求和的时间复杂度都是O(logn)。树状数组是一种折中的方案,当遇到大量计算区间和的题目直接套模板,但对于大量查询某一个元素值却要使用一定的技巧来转化(如下文提到的改段求点)。
再次强调的是,树状数组是一种数据结构,而不是一种算法,是一种工具而不提供解题思路。尤其是在做专题训练时,不要为了用树状数组而去找思路,而应该是先寻找思路,找到思路后,看题目要求,是否要求大量计算区间和,若是,则考虑是否可以用树状数组。
首先是最基本的概念,提供一个不错的博文:点击打开链接
树状数组分一维、二维,甚至可以是三维(三维也可以看做枚举二维的树状数组)。
树状数组的题目类型主要有三种,改点求段,改段求点,改段求段。这里提供一个不错的博客,详细讨论了这三种模型,在此借花献佛 点击打开链接。
树状数组一些注意的地方:update函数和sum函数可以有两种方式,update若是向下更新,sum就是向上求和。update若是向上更新,sum就要向下求和。不同的题目选择不同的组合方式,会使问题简化。其次,树状数组下标从1开始,有些题目下标从0开始,要注意转化一下,否侧会导致程序死循环,TLE。最后,要注意一下二维或者三维求区间段和的方法,方法类似为求矩形的面积。
一些题目
hdu1166 敌兵布阵点击打开链接 模板题
AC代码
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iomanip>
#include<stdlib.h>
#include<set>
#include<map>
#include<queue>
using namespace std;
const int Max=50005;
int c[Max],n;
int lowbit(int x)
{
return x & (-x);
}
void update(int i, int x)
{
while(i<=n)
{
c[i] = c[i]+x;
i += lowbit(i);
}
}
int sum(int i)
{
int ans = 0;
while(i>0)
{
ans += c[i];
//cout<<i<<" "<<c[i]<<' ';
i -= lowbit(i);
}
return ans;
}
int main()
{
int t;
scanf("%d",&t);
int count;
for(count=1;count<=t;count++)
{
memset(c,0,sizeof(c));
int i,j;
scanf("%d",&n);
for(i=1; i<=n; i++)
{
int x;
scanf("%d",&x);
update(i,x);
}
char str[7];
printf("Case %d:\n",count);
while(scanf("%s",&str)&&str[0]!='E')
{
scanf("%d%d",&i,&j);
if(str[0]=='Q')
{
printf("%d\n",sum(j)-sum(i-1));
}
else if(str[0]=='A')
{
update(i,j);
}
else if(str[0]=='S')
{
update(i,-j);
}
}
}
return 0;
}
hdu 1556 color the ball
点击打开链接 典型的改段求点
AC代码
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<iomanip>
#include<algorithm>
#include<cmath>
using namespace std;
const int Max=100005;
int c[Max],n;
int lowbit(int x)
{
return x & (-x);
}
void update(int i, int x)
{
while(i<=n)
{
c[i] = c[i]+x;
i += lowbit(i);
}
}
int sum(int i)
{
int ans = 0;
while(i>0)
{
ans += c[i];
i -= lowbit(i);
}
return ans;
}
int main()
{
int i,j,a,b;
while(scanf("%d",&n) && n)
{
memset(c,0,sizeof(c));
for(i=0; i<n; i++)
{
scanf("%d%d",&a,&b);
update(a,1);
update(b+1,-1);
}
printf("%d",sum(1));
for(i=2; i<=n; i++)
{
printf(" %d",sum(i));
}
printf("\n");
}
return 0;
}
POJ 3067 Japan 求逆序数 解题报告点击打开链接
POJ2299 Ultra-QuickSort 逆序数+离散化 题目链接点击打开链接 解题报告:点击打开链接
POJ 1195 Mobile phones二维树状数组应用 注意求区间和公式的推导 解题报告点击打开链接
UFOs 三维树状数组,有了上一题求区间和的思想,公式就不难推了。解题报告点击打开链接
以上是对这几天学习树状数组的一点总结。树状数组就是帮助我们快速求得区间和的一种工具,但其中的一些处理技巧却值得细细品味。我在看很多博文时一直看不懂,感觉晦涩难懂,但是当我把操作过程模拟一遍之后,大脑就清晰很多了。绝知此事要躬行啊!