树状数组

首先树状数组是一种用于维护前缀信息的的数据结构,其实现十分简洁优美,如下图。

一般来说,树状数组可以解决的问题线段树也可以,但是树状数组的常数会更小。

C1 = A1

C2 = A1 + A2

C3 = A3

C4 = A1 + A2 + A3 + A4

C5 = A5

C6 = A5 + A6

C7 = A7

C8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8

...

C16 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8 + A9 + A10 + A11 + A12 + A13 + A14 + A15 + A16

这里有一个有趣的性质:

设节点编号为x,那么这个节点管辖的区间为2^k(其中k为x二进制末尾0的个数)个元素。因为这个区间最后一个元素必然为Ax,

所以很明显:Cn = A(n – 2^k + 1) + ... + An

这里的2^k就为对应位置的lowbit(pos)

其中我们可以发现,每一个位置都对应一端长度的值,比如我们要求1到7区间的值,我们只需要依次累加d[7],d[6],d[4]的值了,这样的查询效率是我们可以接受的,它不会超过O(logn),那么如何让它累加对应位置的值呢?这时候就要引入lowbit这个神奇的东西了,lowbit是返回二进制对应末尾1最先出现的位置。那么这个有什么用呢?我们来看如果要求1到7区间的值,7对应的二进制是111,6的二进制是110,4的二进制是100,我们不难发现每次只要减去二进制最低位的1就可以实现了!!,那么设当前位置为pos,那么每次只要pos-lowbit(pos)就可以实现了。对于单个位置的数的修改,只需要修改它存在的位置就可以用了,对应位置同理为pos+lowbit(pos)直到边界。

 

一 单点修改,区间查询

函数lowbit()

lowbit是用来取出二进制中最低位数的1所代表的二进制的值。

代码

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

题目链接 https://www.luogu.org/problemnew/show/P3374

模板 

#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
using namespace std;
int d[500005],n,m;
int lowbit(int x)
{
    return x&(-x);
}
void add(int pos,int v)
{
    while(pos<=n){
        d[pos]+=v;
        pos+=lowbit(pos);
    }
}
void input()
{
    int x;
    for(int i=1;i<=n;i++){
        scanf("%d",&x);
        add(i,x);
    }
}
int query(int x)
{   int sum=0;
    while(x){
        sum+=d[x];
        x-=lowbit(x);
    }
    return sum;
}
int main()
{
    scanf("%d%d",&n,&m);
    input();
    int f,x,y;
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&f,&x,&y);
        if(f==1)
            add(x,y);
        else
            printf("%d\n",query(y)-query(x-1));
    }
    return 0;
}

 

二 区间修改,单点查询

这里首先介绍差分

设数组a[]={1,6,8,5,10},那么差分数组b[]={1,5,2,-3,5}

也就是说b[i]=a[i]-a[i-1];(a[0]=0;),那么a[i]=b[1]+....+b[i];

假如区间[2,4]都加上2的话

a数组变为a[]={1,8,10,7,10},b数组变为b={1,7,2,-3,3};

发现了没有,b数组只有b[2]和b[5]变了,因为区间[2,4]是同时加上2的,所以在区间内b[i]-b[i-1]是不变的.

所以对区间[x,y]进行修改,只用修改b[x]与b[y+1]:

b[x]=b[x]+k;b[y+1]=b[y+1]-k;

之后就可以利用树状数组解决了

题目链接 https://www.luogu.org/problemnew/show/P3368

模板

#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
using namespace std;
int d[500005],n,m;
int lowbit(int x)
{
    return x&(-x);
}
void add(int pos,int v)
{
    while(pos<=n){
        d[pos]+=v;
        pos+=lowbit(pos);
    }
}
int query(int x)
{   int sum=0;
    while(x){
        sum+=d[x];
        x-=lowbit(x);
    }
    return sum;
}
int main()
{
    scanf("%d%d",&n,&m);
    int last=0,now;
    for(int i=1;i<=n;i++){
        scanf("%d",&now);
        add(i,now-last);
        last=now;
    }
    int f,x,y,k;
    for(int i=1;i<=m;i++){
        scanf("%d",&f);
        if(f==1){
            scanf("%d%d%d",&x,&y,&k);
            add(x,k);
            add(y+1,-k);
        }
        else{
            scanf("%d",&x);
            printf("%d\n",query(x));
        }
    }
    return 0;
}

 

三 区间修改,区间查询

分析

c是差分数组

a[1]+a[2]+...+a[n]

= (c[1]) + (c[1]+c[2]) + ... + (c[1]+c[2]+...+c[n]) 

= n*c[1] + (n-1)*c[2] +... +c[n]

= n * (c[1]+c[2]+...+c[n]) - (0*c[1]+1*c[2]+...+(n-1)*c[n])    (式子①)

那么我们就维护一个数组c2[n],其中c2[i] = (i-1)*c[i]

每当修改c的时候,就同步修改一下c2,这样复杂度就不会改变

那么

式子①

=n*c1(c,n) - c2(c2,n)

题目链接 http://codevs.cn/problem/1082/

模板

#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
using namespace std;
long long n,m;
long long c1[500005],c2[500005],num[500005];
long long lowbit(long long x)
{
    return x&(-x);
}
void add(long long *s,long long pos,long long v)
{
    while(pos<=n){
        s[pos]+=v;
        pos+=lowbit(pos);
    }
}
long long query(long long *s,long long pos)
{
    long long sum=0;
    while(pos){
        sum+=s[pos];
        pos-=lowbit(pos);
    }
    return sum;
}
int main()
{   long long f,a,b,v;
    scanf("%lld",&n);
    num[0]=0;
    for(int i=1;i<=n;i++){
        scanf("%lld",&num[i]);
        add(c1,i,num[i]-num[i-1]);
        add(c2,i,(i-1)*(num[i]-num[i-1]));

    }
    scanf("%lld",&m);
    for(int i=1;i<=m;i++){
        scanf("%lld",&f);
        if(f==1){
            scanf("%lld%lld%lld",&a,&b,&v);
            add(c1,a,v);
            add(c1,b+1,-v);
            add(c2,a,v*(a-1));
            add(c2,b+1,-v*b);
        }
        else{
            scanf("%lld%lld",&a,&b);
            long long sum1=(a-1)*query(c1,a-1)-query(c2,a-1);
            long long sum2=b*query(c1,b)-query(c2,b);
            printf("%lld\n",sum2-sum1);
        }
    }
    return 0;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值