树状数组
简单来说,树状数组是一种可以解决区间求和和前缀最值问题的数据结构。其大概结构如下图:

所以有:$c[1]=a[1]$
$ c[2]=a[1]+a[2] $
$ c[3]=a[3] $
$ c[4]=a[1]+a[2]+a[3]+a[4]。$
$……$
然后这些关系可以通过lowbit获取。lowbit(x)表示的是$2^k$,k是从最低位到高位的0的个数。比如说$(4)_10=(100)_2$,所以$lowbit(4)=2$。然后我们要从儿子x到达父亲y的话,可以让$x+=lowbit(x)$。
然后下面说树状数组的操作:
树状数组的修改
插入的话就是通过儿子节点不断的往上传,每次到达一个节点然后更新,然后移到这个节点的父亲上。代码如下:
void update(int x,int dat){
while (x<=tot){
c[x]+=dat;
x+=lowbit(x);
}
}树状数组的查询
查询的话是求1~n树状数组中所有值的和。由于c[i]中存的是一部分的区间之和,所以我们只要对这部分节点求和就行了。
比如说上图中对1~7求和,易知$ans=c[7]+c[6]+c[4]$,然后找到这些点只需要让$x-=lowbit(x)$就行了。代码如下:
int getNum(int x){
int ret=0;
while (x>0){
ret+=c[x];
x-=lowbit(x);
}
return ret;
}然后知道两种操作之后我们就可以搞一些高端操作了,比方说:
单点修改和区间求和
这个直接用上面两种操作就行了。
区间修改和单点查询
这个可以用差分数组来维护,不过上面查询查出来的值是单点的值。
树状数组求逆序对
比如说给出一列数,要求逆序对。应该先对数进行离散化(获取数间大小关系的一种操作),然后按排名插入树状数组的对应位置上。每次插入之后统计一下已经插入的比这个数大的数的个数,最后统计的结果就是逆序对的个数
区间修改和区间查询
这个的话依然是考虑用差分数组来维护,不过区间查询的话要另外维护一个数组
因为$c[i]=a[i]-a[i-1]$
所以$\sum_{i=1}^n a[i]\quad$
$=a[1]+a[2]+…+a[n]$
$=c[1]+c[1]+c[2]+…+a[1]+a[2]+…+a[n]$
$=n*c[1]+(n-1)*c[2]+…+a[n]$
$=\sum_{i=1}^n (n-i+1)*(c[i])\quad$
$=\sum_{i=1}^n n*c[i]\quad-\sum_{i=1}^n (i-1)*c[i]\quad$
所以额外维护一个$(i-1)*c[i]$的数组就行了。求和就直接套上面的公式
代码:
int update(int x,int dat){
while (x<=m){
c[x]+=dat;
d[x]+=dat*(x-1);
x+=lowbit(x);
}
}二维树状数组
这个就和一维的树状数组差不多,把操作稍微改一下就行了。代码:
int getNum(int x,int y){
int ret=0;
for (int i=x;i>0;i-=lowbit(i))
for (int j=y;j>0;j-=lowbit(j))
ret+=c[i][j];
return ret;
}
void update(int x,int y,int dat){
for (int i=x;i<=n;i+=lowbit(i))
for (int j=y;j<=n;j+=lowbit(j))
c[i][j]+=dat;
}例题
HDU1556
POJ2299
POJ3067
POJ3321 <-这个题还挺有意思的
POJ2155
Views: 62