详解树状数组 区间修改求和


呃.... 我这个人 ... 看到好东西就想转.....

其实这个问题自己之前研究了一下 懒得写 转载一篇吧...


从前有个东西叫树状数组,它可以轻易实现一些简单的序列操作,比如单点修改,区间求和;区间修改,单点求值等.

但是我们经常需要更高级的操作,比如区间修改区间查询.这时候树状数组就不起作用了,只能选择写一个2000GB的线段树交上去然后被卡常—–或者另一个选择是写ZKW线段树,会好一些.

再但是…谁告诉你树状数组不能区间修改区间求和?告诉你,树状数组不仅能实现,而且代码依旧那么短小精悍.

今天我们就来研究研究,如何实现这个更划算的数据结构.
我们已经学会了树状数组的基本操作:单点修改区间查询,或区间修改单点查询(不会的话先去自学吧…这篇文章不适合你…).思考,区间修改单点求值是怎么做到的?只需要维护一个新数组c[i]=a[i]-a[i-1],也就是c[]是a[]的差分数组,修改区间[l,r]+v只需

add(l,v);add(r+1,v)

即可.求某个值的时候,只需要把差分数组的前缀和求出来,就是要求的了.
领悟了这个操作以后我们发现,化区间为单点的思想精髓就在于差分二字.利用差分思想,区间修改解决了,接下来就是区间求和公式的推导过程:
sum(1,n)
=a[1]+a[2]+a[3]+…+a[n-1]+a[n]
=c[1]+(c[1]+c[2])+…+(c[1]+c[2]+…+c[n])
=n*(c[1]+c[2]+…+c[n])-(0*c[1]+1*c[2]+2*c[3]+…+(n-1)*c[n]).
发现什么了?
我们开第二个树状数组c2,令c2[i]=c[i]*(i-1),那么…

区间修改[l,r]+=v:
add(c[l],v),add(c[r+1],-v);
add(c2[l],(l-1)*v),add(c2[r+1],-r*v);

求前缀和sum(1,n):
sum(1,n)=n*query_c(n)-query_c2(n).

求区间和sum(l,r):
sum(l,r)=sum(r)-sum(l-1).

至此,树状数组已经轻松实现了区间修改区间求和!

例题:luogu 3372线段树模板 这题用线段树写500+ms,拿裸的树状数组311ms就切掉了,代码也养眼得多.至于zkw的效率如何我不太清楚.

顺便:其实一开始建树的时候没必要把原来数组的元素一个个扔进树里,直接维护一个前缀和,然后计算的时候加上这个前缀和就好了.省去了nlogn的建树操作,会快很多.此处为了增强代码可读性,没有加这一句.



#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 102333
using namespace std;
typedef long long ll;
int n,m;
ll a[N],c1[N],c2[N];
inline int lowbit(int x){return x&(-x);}
void add(ll *r,int pos, ll v)
{
    for(;pos<=n;pos+=lowbit(pos))r[pos]+=v;
}
ll getsum(ll *r,int pos)
{
    ll re=0;
    for(;pos>0;pos-=lowbit(pos))re+=r[pos];
    return re;
}
ll sigma(int r)
{
    ll sum1=r*getsum(c1,r),sum2=getsum(c2,r);
    return sum1-sum2;
}
ll query(int x,int y)
{
    return sigma(y)-sigma(x-1);
}
int flag,x,y;ll k;
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&a[i]);
        add(c1,i,a[i]-a[i-1]);
        add(c2,i,(i-1)*(a[i]-a[i-1]));
    }
    for(int i=1;i<=m;i++)
    {
        scanf("%d",&flag);
        if(flag==1)
        {
            scanf("%d%d%lld",&x,&y,&k);
            add(c1,x,k);add(c1,y+1,-k);
            add(c2,x,(x-1)*k);add(c2,y+1,y*(-k));
        }
        else
        {
            scanf("%d%d",&x,&y);
            printf("%lld\n",query(x,y));
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值