关于树状数组

线段树和树状数组在noip中本人觉得挺重要的,而且比较难写。

我们先来看树状数组。

 

look at 这个图

 

那么我们会发现一个有趣的性质。

设节点编号为n,那么这个节点管辖的区间为2k(其中k为n二进制末尾0的个数)个元素。

这个区间最后一个元素必然为An。

所以:Cn=A(n-2k+1)+...An

神奇吧。

通常,我们管2k叫做lowbit(n),哇这个名字真好听。

计算方法呢就是n&(-n) 

原理呢就是计算机中的补码存储方式。->挺好的

关于查询:

这个时候我们要用到前缀和,通过查询前缀和来实现查询区间和。

那么我们如何实现呢,前缀和相减即可。

关于修改:

代码:(add与query)

众所周知(在我看来),树状数组不论是空间还是时间,都是完胜线段树的,那么我们就来分析一下它的复杂度:

1.查询的时候,因为每次都会让二进制表示中的最后一个1变为0,所以是O(log2n)。

2.修改的时候,同样也是每次让二进制表示中的最后一个1变为0,所以是O(log2n)。

相比于其他大型数据结构如线段树啊,splay啊,他在常数上非常有优势。

不过缺点是,他的应用面比较窄,基本是只能维护数字之后这一种信息。你可以在各大网站的模板题中看出来。

代码实现:

洛谷 模板1

#include<cstdio>
#include<iostream>
int n,m,a[500010];
void add(int l,int r) {
    while(l<=n) a[l]+=r,l+=l&(-l);
}
int sum(int x) {
    int num=0;
    while(x) num+=a[x],x-=x&(-x);
    return num;
}
int main() {
    scanf("%d%d",&n,&m);
    for(int x,i=1;i<=n;i++) 
        scanf("%d",&x),add(i,x);
    for(int i=1,a,b,c;i<=m;i++){
        scanf("%d%d%d", &a, &b, &c);
        if(a==1) add(b,c);
        else printf("%d\n",sum(c)-sum(b-1));
    }
    return 0;
}

洛谷 模板2

#include <iostream>
#include <cstdio>
#define lowbit(x) x & -x
using namespace std;
long long tree[500005];
int n, m;
void add(int x, long long num) {
    while (x <= n) {
        tree[x] += num;
        x += lowbit(x);
    }
}
long long query(int x) {
    long long ans = 0;
    while (x) {
        ans += tree[x];
        x -= lowbit(x);
    }
    return ans;
}
int main() {
    freopen("in.txt", "r", stdin);
    scanf("%d%d", &n, &m);
    long long last = 0, now;
    for (int i = 1; i <= n; i++) {
        scanf("%lld", &now);
        add(i, now - last);
        last = now;
    }
    int flg;
    while (m--) {
        scanf("%d", &flg);
        if (flg == 1) {
            int x, y;
            long long k;
            scanf("%d%d%lld", &x, &y, &k);
            add(x, k);
            add(y + 1, -k);
        } else if (flg == 2) {
            int x;
            scanf("%d", &x);
            printf("%lld\n", query(x));
        }
    }
    return 0;
}

 

上面我们讨论了一维的,下面我们来讨论一下二维的。

 

其实很简单,就是树状数组套树状数组。

 

代码:

 

 

具体实现:

 

Mobile phones

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define N 1100
int n,c[N][N];
inline int lowbit(int x){
    return x&-x;
}
void updata(int x,int y,int v){
    for(int i=x;i<=n;i+=lowbit(i)){
        for(int j=y;j<=n;j+=lowbit(j)){
            c[i][j]+=v;
        }
    }
}
int sum(int x,int y){
    int res=0;
    for(int i=x;i;i-=lowbit(i)){
        for(int j=y;j;j-=lowbit(j)){
            res+=c[i][j];
        }
    }
    return res;
}
int getsum(int x1,int y1,int x2,int y2){
     return sum(x2,y2)-sum(x1-1,y2)-sum(x2,y1-1)+sum(x1-1,y1-1);
}
int main(){
    int opt,l,r,x,y,a,b,t;
    while(scanf("%d",&opt)!=EOF){
        if(opt==0){
            scanf("%d",&n);
            memset(c,0,sizeof(c));
        }
        else if(opt==1){
            scanf("%d%d%d",&x,&y,&a);
            updata(x+1,y+1,a);
        }
        else if(opt==2){
            scanf("%d%d%d%d",&l,&b,&r,&t);
            printf("%d\n",getsum(l+1,b+1,r+1,t+1));
        }
    }
    return 0;
}

总结:树状数组作为一种方便、高效的数据结构,通常和其它的一些算法一起使用,而将树状数组作为主算法的题目反而较少。熟练掌握树状数组,是每个选手想要成功应该做到的。

下一篇我们要讲树状数组的升级版,线段树,拥有比树状数组更强大的功能,而且比较好理解,只不过更繁琐一些,在复杂度方面不如树状数组。

 

一世安宁

 

posted @ 2018-08-09 20:21  GTBA  阅读(196)  评论(0编辑  收藏  举报