树状数组 区间修改

愚蠢的我花了好久好久才看明白他的意思….
最近不是在弄树状数组嘛,然后这个区间修改,怎么也想不明白,今天看了一下午博客,总算弄懂了.
让我们开始.我会尽量使用常数而不是变量,为了让你完全清楚.
如果出现公式,都不难,如果你一时看不清,可以拿笔写一写,保证没有任何难度.
我们约定,a[i]代表数据本身,(为了和数组数组中的数据区别)
首先,我们要实现区间修改,那么我们就不能和以前一样,我们假设一个c数组,这里面c[i],代表着a[i]-a[i-1].

问题来到,为什么我们要构造c[i].
想一想,假如我要对a[l]+…+a[r]进行区间修改,怎么办?
首先,c[i],是a[i]数组的一个映射,也就是说,当我们使用a[i]来建立树状数组时,a[i]表现的是每个节点的数字本身,而我们的c[i],则是表现每个节点的数字之间的差.那么我们可以想象,假如a[l]~a[r],每个都加x,那么在用a[i]表示节点数字性质时,我们理所当然的就是每个都加x,但是我们现在用c[i]表示节点数字性质时,你会发现,除了c[l]会加x,c[r+1]会减x外,其他地方,都没有变化.这便是关键了.
结论,利用c[i]作为a[i]的映射,我们可以通过两次修改,达到原来的n次修改的效果.那么接下来,就是我们区间修改的具体实现了.

可以认为接下来的一切,并没有显著的逻辑,他只是就是这样而已.

当我们有了这个定义之后,显然的,a[2] = c[2]+c[1],a[3] = c[3]+c[2]+c[1].那么这时候,我们想要计算a[1]+a[2]+….+a[n],也就是
c1 + (c2+c1)+(c3+c2+c1)+(…)+(Cn+Cn-1+…+C1),对吧?

我们可以进行整合,将他变为
n*(c1+c2+c3+…+cn)-(0*c1+1*c2+2*c3+…+(n-1)*Cn).
为什么这里关键?你观察会发现,这是两个前缀和的形式,一个是c1+c2+..+cn,一个是(1-0)*c1+(2-1)*c2+…+(n-1)*Cn.
那么说道前缀和,我们树状数组要上场了!
我们不妨假设d[i]= (i-1)*c[i].
显然,我们维护一个c[i]数组对应的树状数组(注意他们两个不是一个东西),就可以很快的对c[i]进行求和,同时,建立c[i]的映射,d[i]的树状数组,也可以很快求出他的前缀和了.

具体看代码,这是一个模版题的代码.
codevs,区间修改区间求和

/*  xzppp  */
#include <iostream>
#include <vector>
#include <cstdio>
#include <string.h>
#include <algorithm>
#include <queue>
#include <map>
#include <string>
#include <cmath>
#include <bitset>
#include <iomanip>
using namespace std;
#define FFF freopen("in.txt","r",stdin);freopen("out.txt","w",stdout);
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define MP make_pair
#define PB push_back
typedef long long  LL;
typedef unsigned long long ULL;
typedef pair<int,int > pii;
typedef pair<double,double > pdd;
typedef pair<double,int > pdi;
const int MAXN = 200000+17;
const int MAXM = 20;
const int MAXV = 2*1e3+17;
const int INF = 0x7fffffff;
const int MOD = 1e9+7;
LL bit[2][MAXN],n,a[MAXN];
void add(int k,int p,LL x)
{
    while(p<=n)
    {
        bit[k][p] += x;
        p += p&-p;
    }
}
LL sum(int k,int p)
{
    LL res = 0;
    while(p>0)
    {
        res += bit[k][p];
        p -= p&-p;
    }
    return  res;
}
void additv(int from,int to,LL x)
{
    add(0,from,x);
    add(0,to+1,-x);
    add(1,from,(from-1)*x);
    add(1,to+1,(to)*(-x));
}
LL sumitv(int from,int to)
{
    LL res = 0;
    res += to*sum(0,to);
    res -= sum(1,to);
    res -= (from-1)*sum(0,from-1);
    res += sum(1,from-1);
    return res;
}
int main()
{
    #ifndef ONLINE_JUDGE 
    FFF
    #endif
    int m;
    cin>>n;
    for (int i = 1; i <= n; ++i)
    {
        scanf("%lld",a+i);
        add(0,i,a[i]-a[i-1]);
        add(1,i,(i-1)*(a[i]-a[i-1]));
    }
    cin>>m;
    for (int i = 0; i < m; ++i)
    {
        LL cmd,a,b,c;
        scanf("%lld",&cmd);
        if(cmd==1)
        {
            scanf("%lld%lld%lld",&a,&b,&c);
            additv(a,b,c);
        }
        else
        {
            scanf("%lld%lld",&a,&b);
            printf("%lld\n",sumitv(a,b));
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值