树状数组 小白篇(2)暨区间修改

这篇主要来讲一讲树状数组的区间修改

因为一个一个点改,毫无疑问耗时太长

所以,机智的人类YY出了用差分来表示数组

为了便于理解,简单一点数组{an}:a[1]=0, a[2]=0, a[3]=0, a[4]=0, a[5]=0, a[6]=0 ,a[7]=0, a[8]=0, a[9]=0

用差分思想,delta[x]表示a[x]-a[x-1]

显然,一开始delta[]=0

我们先计算出前n项和{sn}来

然后别眨眼,下面就是见证奇迹的时刻!

有个分界线显得正式一点


我们要把a[3]到a[6]整段提高3个单位长度,就让delta[3]+=3, delta[7]-=3

想象成阶梯,一端抬起,另一端就压下去。(自行脑补)

那么现在的a[x]+=delta[1]+delta[2]+……+delta[x-1]+delta[x]

我现在要得到前7项的和,s[7]+=每一项应该加的差分

          暨s[7]+=(delta[1])+(delta[1]+delta[2])+(delta[1])+delta[2]+delta[3])+……+(delta[1]+delta[2]+delta[3]+delta[4]+delta[5]+delta[6]+delta[7])

我还是画个图吧(为了方便说明只画差分的)

显然搬过砖的都知道,这个砖不好搬,因为它不方

那我们就把它变方来

(为什么那么丑,因为我不会画画以及懒)

额妹子!现在就好表示这堆砖了!!!!!!!!!

(7+1)(∑delta【1~7】)-(delta【1】*1+delta【2】*2+……+delta【7】*7)          (注意是7+1,不是7!!!!!!!!!)

后面的delta【n】*n很难看是不是?

那就表示成deltai【】

定义deltai【i】=delta【i】*i

显然因为要大量求和,所以我们把deltai和delta维护成树状数组

那么

1 // 修改:把[l, r]区间均加上x     
2 Update(delta, l, x);       
3 Update(delta, r+1, -x);  
4 Update(deltai, l, x * l);  
5 Update(deltai, r+1, -x * (r+1)); 
6 //updata相当于上一篇的add
// 查询:[l, r]区间和     
long long suml = s[l - 1] + l * Query(delta, l - 1) - Query(deltai, l - 1);  
long long sumr = s[r] + (r + 1) * Query(delta, r) - Query(deltai, r);  
ans=sumr - suml;  
//query相当于上一篇的sum

是不是很神奇?

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define lowbit(i) (i&-i)
typedef long long ll;
using namespace std;
const int N = 500005;
int n,m;
ll sum[N];
ll delta[N],deltai[N];
ll query(ll *p,int pos)
{
    ll ans=0;
    while(pos>0)
    {
        ans+=p[pos];
        pos-=lowbit(pos);
    }
    return ans;
}
void updata(ll *p,int pos,int x)
{
    while(pos<=n)
    {
        p[pos]+=x;
        pos+=lowbit(pos);
    }
}
int main()
{
    int i,l,r,v;
    ll hhh;
    int s1,s2;
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;i++)
        scanf("%lld",&hhh),sum[i]=sum[i-1]+hhh;
    while(m--)
    {
        scanf("%lld",&hhh);
        if(hhh==1)                      //1表示修改,l至r闭区间加上v
        {
            scanf("%d%d%d",&l,&r,&v);
            updata(delta,l,v);
            updata(delta,r+1,-v);              //注意边界
            updata(deltai,l,v*l);
            updata(deltai,r+1,-v*(r+1));
        }
        else
        {
            scanf("%d%d",&l,&r);                        //l至r的和
            s1=sum[l-1]+l*query(delta,l-1)-query(deltai,l-1);      //注意边界,不理解就看上面的图
            s2=sum[r]+(r+1)*query(delta,r)-query(deltai,r);
            printf("%lld
",s2-s1);
        }
    }
    return 0;
}

感谢

“每天心塞一点点”的博客(我喜欢这个名字)

参考博文地址

原文地址:https://www.cnblogs.com/callmebg/p/7229128.html