树状数组复习小计

简介

其实我以前以为树状数组是一个很没用的东西,今天一看才发现可以干很多事情。
树状数组详细是什么我就不说了,网上还有很多资料。大致就是,每个点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的文件管理

由于本人是个蒟蒻

目前就知道这么多,好像还有一个树状数组套主席树变成支持区间修改的主席树,不过我没有打过……

你可能感兴趣的:(树状数组,莫队算法,树状数组求逆序对,复习小计,树状数组求最值)