树状数组简介(洛谷P3368、P3374)

来源:互联网 发布:安倍经济学知乎 编辑:程序博客网 时间:2024/06/03 22:55

算法用途

树状数组是一个查询和修改复杂度都为log(n)的数据结构。主要用于查询任意两位之间的所有元素之和。

虽然树状数组的用途可以完全被线段树所替代,而且线段树所能做的比树状数组多得多,但是树状数组的常数是远远小于线段树的。因此当你写线段树被卡常时可以试试使用树状数组。

而且树状数组的代码很短哦!

算法思想

开一个数组s,其中s[i]的值存的是a[i-lowbit(i)]~a[i]这段区间的和。然后进行一系列操作。

差不多是这样一个图(这里是c数组):

图片转自百度

像不像一棵树啊#(滑稽)

关于lowbit

lowbit是什么啊!看上去好高级的样子!
low:低,bit:比特(二进位制信息单位)(其实就是1啦) lowbit就是一个数在二进制中最低的1的位置。
举个栗子:5在2进制下为101,lowbit(5)=1。8在二进制下为1000,lowbit(8)=4。
那要怎么算lowbit(x)呢?
给段代码自己理解下

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

对,你没看错,就是这么简单!可能会补码和反码的同学已经看懂了,这里给一脸蒙圈的解释一下。
设x的二进制码为 1000101011,那么-x-1的二进制码就是把x的二进制码全反过来:0111010100,-x的二进制码就是:0111010101,x&(-x)=1,就是x的lowbit了。(不信的小伙伴可以自己试试)

单点修改区间求和

单点修改

当a[x]改变时,s[x]会改变,s[x+lowbit(x)]会改变,s[x+lowbit(x)+lowbit(x+lowbit(x))]也会改变······一直到x超过n才停止。
于是我们就可以尝试写出代码:

void nsrt(int x,int w){    while (x<=n){        s[x]+=w;        x+=lowbit(x);    }}

区间求和

树状数组的区间求和运用到了前缀和的思想,求l~r的和即求1~r的和减去1~(l-1)的和。
那么1~x的和怎么求呢?
前面说过,s[x]存的是a[x-lowbit(x)]~a[x]的和,那么a[x-lowbit(x)]~a[x]的和我们是已知的,剩下就是求1~(x-lowbit(x))的和,这时s[x-lowbit(x)]又变成了已知,因此和单点修改一样,区间求和也是一个递归的过程,代码依然很短:

int srch(int x){    int sum=0;    while(x){        sum+=s[x];        x-=lowbit(x);    }    return sum;}

模板

洛谷P3374

#include<cstdio>#include<cstring>#include<algorithm>#define MAXN 500000 using namespace std;int s[MAXN+5];int next;int n,m;int lowbit(int x){    return x&(-x);}void nsrt(int x,int w){    while (x<=n){        s[x]+=w;        x+=lowbit(x);    }}int srch(int x){    int sum=0;    while(x){        sum+=s[x];        x-=lowbit(x);    }    return sum;}int main(){    scanf("%d%d",&n,&m);    for (int i=1;i<=n;i++){        int x;        scanf("%d",&x);        nsrt(i,x);    }    for (int i=1;i<=m;i++){        int flag;        scanf("%d",&flag);        if (flag==1){            int x,k;            scanf("%d%d",&x,&k);            nsrt(x,k);        }        else{            int x,y;            scanf("%d%d",&x,&y);            printf("%d\n",srch(y)-srch(x-1));                    }    }    return 0;} 

区间修改区间求和

百度上说区间修改时只能求单点值,但是貌似区间也是可以的。。。
因为博主比较懒中间过程比较烦,这里博主就不写了,
引用一下某大佬的blog

模板:

洛谷P3368

#include<cstdio>#include<algorithm>#include<cstring>#define MAXN 500000using namespace std;int s[MAXN+5][3];int n,m;int lowbit(int x){    return x&(-x);}void nsrt(int l,int r,int w){    int x;    r++;    x=l;    while (x<=n){        s[x][1]+=w;         s[x][2]+=w*(l-1);         x+=lowbit(x);    }    x=r;    while (x<=n){        s[x][1]-=w;        s[x][2]-=w*(r-1);        x+=lowbit(x);    }}int srch(int x){    int now=x,sum=0;    while (now){        sum+=x*s[now][1]-s[now][2];        now-=lowbit(now);    }    return sum;}int main(){    scanf("%d%d",&n,&m);    for (int i=1;i<=n;i++){        int x;        scanf("%d",&x);        nsrt(i,i,x);    }    for (int i=1;i<=m;i++){        int flag;        scanf("%d",&flag);        if (flag==1){            int x,y,k;            scanf("%d%d%d",&x,&y,&k);            nsrt(x,y,k);        }        else{            int x;            scanf("%d",&x);            printf("%d\n",srch(x)-srch(x-1));                    }    }    return 0;}

如果嫌弃博主的文章可以看看这位大佬的blog