模板 - 数据结构 - 树状数组

维护一些满足结合律又满足结合律的群,也就是交换群,与线段树不同,线段树只需要满足结合律,是幺半群

原因在于线段树的区间操作是真正的从小区间合并上来的,但是树状数组是来源于两个前缀和作差,这就要求这个“前缀和”拥有逆元,也就是“减法”。

这样,拥有逆元运算的就可以使用树状数组维护。

单点加值,区间查询:

初始化的时候手动把每一个值add上去就可以了。

const int MAXN=500000;
int a[MAXN+5];
int c[MAXN+5];

int n;

inline int lowbit(int x) {
    return (x & -x);
}

//a[x]+=k
void add(int x, int k) {
    for(int i=x;i<=n;i+=lowbit(i))
        c[i]+=k;
}

int get_sum(int x) {
    int ans = 0;
    for(int i=x;i;i-=lowbit(i))
        ans+=c[i];
    return ans;
}

inline int range_query(int l,int r) {
    return get_sum(r)-get_sum(l-1);
}

求逆序对:

好像是求一整个数组的逆序对,留坑。

求区间最大/最小值:

这个是个假的区间最大/最小值,这个只能求[1,n],没啥用,不写了。

区间加值,单点查询:

利用差分的思想很容易实现,初始化的时候也可以每一个值手动add上去。

const int MAXN=500000;
int a[MAXN+5];
int c[MAXN+5];

int n;

inline int lowbit(int x) {
    return (x & -x);
}

void add(int x, int k) {
    for(int i=x;i<=n;i+=lowbit(i))
        c[i]+=k;
}

void range_add(int l,int r,int k){
    add(l,k),add(r+1,-k);
}

int get_sum(int x) {
    int ans = 0;
    for(int i=x;i;i-=lowbit(i))
        ans+=c[i];
    return ans;
}

inline int query(int x) {
    return get_sum(x);
}

示例:

记得是从1开始的。

#include<bits/stdc++.h>
using namespace std;
#define ll long long

const int MAXN=500000;
int a[MAXN+5];
int c[MAXN+5];

int n;

inline int lowbit(int x) {
    return (x & -x);
}

//a[x]+=k
void add(int x, int k) {
    for(int i=x;i<=n;i+=lowbit(i))
        c[i]+=k;
}

int get_sum(int x) {
    int ans = 0;
    for(int i=x;i;i-=lowbit(i))
        ans+=c[i];
    return ans;
}

inline int range_query(int l,int r) {
    return get_sum(r)-get_sum(l-1);
}


int main() {
    int m;
    while(~scanf("%d%d",&n,&m)){
        memset(a,0,sizeof(a));
        memset(c,0,sizeof(c));
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
            add(i,a[i]);
        }

        while(m--){
            int ins;
            scanf("%d",&ins);
            if(ins==1){
                int x,k;
                scanf("%d%d",&x,&k);
                add(x,k);
            }
            else{
                int x,y;
                scanf("%d%d",&x,&y);
                printf("%d\n",range_query(x,y));
            }
        }
    }
}
View Code

 

 

区间加值,区间查询:

考虑上面的区间加值,单点查询,我们要求的其实是query的前缀和,那么每个差分被用的次数是递减的。维护一个次数递增的差分和次数不变的差分,作差可以获得次数递减的差分。

const int MAXN=500000;
int a[MAXN+5];
int c[MAXN+5];
int c2[MAXN+5];

int n;

inline int lowbit(int x) {
    return (x & -x);
}

void add(int x, int k) {
    for(int i=x;i<=n;i+=lowbit(i))
        c[i]+=k,c2[i]+=k*x;
}

void range_add(int l,int r,int k){
    add(l,k),add(r+1,-k);
}

int get_sum(int x) {
    int ans = 0;
    for(int i=x;i;i-=lowbit(i))
        ans+=(x+1)*c[i]-c2[i];
    return ans;
}

inline int query(int l,int r) {
    return get_sum(r)-get_sum(l-1);
}

 


 

 

二维树状数组:

留坑。

参考资料:

https://blog.csdn.net/bestsort/article/details/80796531

posted @ 2019-03-14 23:11  韵意  阅读(262)  评论(0编辑  收藏  举报