区间修改区间求和的树状数组

好像树状数组虽然常数小,编程简单,可是资瓷的操作有限,

普通的树状数组只资瓷单点修改和区间查询,首先要将其升级为区间修改

我们利用差分来进行

定义差分数组b[i]=a[i]-a[i-1]

这样$ a[j]=\sum_{i=1}^jb[i] $

这样我们只要用树状数组维护一下b[i]的前缀和就好了

修改区间[l,r]时我们只需要add(l,x)和add(r+1,-x)就好了

题目链接

就是一个树状数组资瓷区间修改和单点查询的模板

但是这还不够

我们还要区间求和

我们来看

$ \sum_{i=1}^ja[i]=\sum_{i=1}^j\sum_{k=1}^ib[k] $

我们将上面的式子可以写成

$ \sum_{i=1}^ja[i]=\sum_{i=1}^jb[i]*(j-i+1) $

但是上面的式子还是不好维护

我们将其化简为

$ \sum_{i=1}^ja[i]=j*\sum_{i=1}^jb[i]-\sum_{i=1}^jb[i]*(i-1) $(自己推一下,验证一下)

这样我们就可以利用两个树状数组完成区间修改,查询的操作

用两颗树状数组A,B,A维护b[i],B维护b[i]*(i-1)

我们可以很容易得到查询结果就是A(r)*r-B(r)-A(l-1)*(l-1)+B(l-1)

关于修改操作

我们按之前的讨论维护即可

附上代码

题目链接

# include<iostream>
# include<cstdio>
# include<cmath>
# include<algorithm>
const int mn = 100005;
int n,m;
struct Binary_tree{
long long tr[mn];
void add(int i,long long x)
{
    while(i<=n)
    {
        tr[i]+=x;
        i+=i&-i;
    }
}
long long qsum(int i)
{
    long long ret=0;
    while(i>0)
    {
        ret+=tr[i];
        i-=i&-i;
    }
    return ret;
}
}A,B;
long long a[mn];
int main()
{
    int opt,x,y,z;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
       scanf("%lld",&a[i]);
    }
    for(int i=1;i<=n;i++)
    {
        A.add(i,a[i]-a[i-1]);
        B.add(i,(a[i]-a[i-1])*(i-1));
    }
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&opt,&x,&y);
        if(opt==1)
        {
            scanf("%d",&z);
            A.add(x,z);
            A.add(y+1,-z);
            B.add(x,z*(x-1));
            B.add(y+1,-z*y);
        }
        else {
        printf("%lld\n",(A.qsum(y)*y-(x-1)*A.qsum(x-1))-B.qsum(y)+B.qsum(x-1));
        }
    }
    return 0;
}

 

 

 

 

 

posted @ 2018-05-06 11:34  logeadd  阅读(352)  评论(0编辑  收藏  举报