其实我以前以为树状数组是一个很没用的东西,今天一看才发现可以干很多事情。
树状数组详细是什么我就不说了,网上还有很多资料。大致就是,每个点x的父边连向的点都是x+lowbit(x)。lowbit(x)=-x and x,就是把x末尾的1变成0并向上推一位,比如lowbit(100)=1000,lowbit(10101)=10110,lowbit(11000)=100000。
(只能看看手打图了,不知道为什么图传不上去。)
8
/ \ \
4 6 7
/ /
2 5
/ \
1 3
如果不懂推一推他们的二进制就明白了。
一般都用一个数组来存储每个点的一个值,比如我用数组h来表示。
用每个h来存储所有的子节点的和。然后在用前缀和思想就可以实现。
int lowbit(int x){
return (-x)&x;
}
求lowbit
void add(int x,int y)
{
while(x <= n)
{
h[x]+=y;
x+=lowbit(x);
}
}
这个是在x节点添加的值为y,他们他的所有父节点都要加上这个值y。
int sum(int x)
{
int rt=0;
while(x>0)
{
rt+=h[x];
x-=lowbit(x);
}
return rt;
}
这个是求1到x的和。
一般树状数组只用得到这两个操作。
void init(int n){
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j+=lowbit(j)){
h[j]=max(h[j],a[i]);
}
}
}
插入操作类似求和,只是每个节点存储的是最值。
int query(int l,int r){
int ans=a[r];
while(1){
ans=max(ans,a[r]);
if(r==l)break;
for(r-=1;r-l>=lowbit(r);r-=lowbit(r)){
ans=max(ans,h[r]);
}
}
return ans;
}
我们会发现求区间最大值麻烦一点,因为区间最大值不符合前缀和的特性。那么我们可以像倍增那样不断的缩小范围。我们可以发现h[x]的值它控制的范围是[x-lowbit(x)+1,x],那么假如x-lowbit(x)+1小于左范围l那就不管它,继续缩小右范围。如果x-lowbit(x)+1大于左范围l,那么就更新答案,并且缩小范围。知道l=r为止。
求逆序对的思路是:用h来存储前面有多少个比自己小的或等于自己的数,那么用序列的长度减去这个数就是前面有多少个比自己大的数,那么就是逆序对的个数。
void add(int x,int z){
int i,j,k;
while(x<=n){
h[x]+=z;
x+=lowbit(x);
}
}
在x这个点插入z这个值(其实z在这里的值为1啦),然后沿着他的父边不断的加1,那么就相当于给后面的比自己大的数加1,(注意这里h的下标不是原序列,这里相当于一个桶,每个h[x]存储比x这个数小的数的个数)。
int find(int x){
int i,j,k=0;
while(x>0){
k+=h[x];
x-=lowbit(x);
}
return k;
}
这个就是找比x小的数的个数啦,根求和的样子差不多啦。
这个需要树状数组加上莫队算法,详见Mato的文件管理
目前就知道这么多,好像还有一个树状数组套主席树变成支持区间修改的主席树,不过我没有打过……