懒得打思路,在代码后面有 EnjoyingAC 写的,大致意思都是一样的,就找过来附在下面了,看不懂代码的可以看看下面的解释
//区间修改,区间查询
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int N = 200000+5;
typedef long long LL;
LL c[N][2];
int n, m;
int lowbit(int x)
{
return x&(-x);
}
void Update(int x, int k, int pos)
{
while(x <= n)
{
c[x][pos] += k;
x += lowbit(x);
}
}
LL query(int x, int pos)
{
LL sum=0;
while(x > 0)
{
sum += c[x][pos];
x -= lowbit(x);
}
return sum;
}
LL query(int x)
{
LL sum=0;
int tmp = x+1;
while(x > 0) {
sum += c[x][0];
x -= lowbit(x);
}
sum *= tmp;
tmp--;
while(tmp > 0){
sum -= c[tmp][1];
tmp -= lowbit(tmp);
}
return sum;
}
LL querySQ(int l, int r){
// return query(r)-query(l-1);
return ((r+1)*query(r, 0)-query(r,1) - ((l)*query(l-1, 0)-query(l-1,1)));
}
int main()
{
scanf("%d", &n);
int a=0, b, op, x;
for(int i=1; i<=n; i++)
{
scanf("%d", &b);
//差分用于区间修改
Update(i, b-a, 0);
Update(i, (b-a)*i, 1);
a=b;
}
scanf("%d", &m);
for(int i=1; i<=m; i++)
{
scanf("%d", &op);
int l, r;
if(op == 1)
{
scanf("%d%d%d", &l, &r, &x);
Update(l, x, 0);
Update(r+1, -x, 0);
Update(l, x*l, 1);
Update(r+1, -x*(r+1), 1);
}
else if(op == 2)
{
scanf("%d%d", &l, &r);
printf("%lld\n", querySQ(l, r));
}
}
return 0;
}
//思路如下:
转自https://blog.csdn.net/qq_37685156/article/details/80397765
区间修改
任务:
对于数组a[1],a[2],a[3],….a[n],给定区间[l,r]。
要求将a[l]~a[r]的值都加v。
解决:
引入一个差分数组b,b[i]=a[i]-a[i-1](默认a[0]=0)。
比如:
a:1,2,3,4,5
b: 1,1,1,1,1
将区间[2,3]的值都加2
a: 1,4,5,4,5
b: 1,3,1,-1,1
对于a中区间[l,r]都加v,b数组中b[l]+=v,b[r+1]-=v.
故只需对原数组a的差分数组b用树状数组去进行单点修改,就可以快速地对数组a进行区间修改。
单点查询
任务:
对进行了区间修改的数组进行单点查询
解决:
观察差分数组,可以推出:a[i]=b[1]+b[2]+…+b[i]。
故只需求出原数组a的差分数组b的前缀i即可查询a[i]。
区间查询
任务:
对进行了区间修改的数组进行区间查询
解决:
引入第二个数组c,c[i]=i*b[i].称为数组b的i倍数组。
比如:
a:1,2,3,4,5
b:1,1,1,1,1
c:1,2,3,4,5
将区间[2,3]的值都加2后,
a:1,4,5,4,5
b:1,3,1,-1,1
c:1,6,3,-4,5
查询数组a的前3位数的和,即a[1]+a[2]+a[3]=1+4+5=10。
a[1]+a[2]+a[3]=4*(b[1]+b[2]+b[3]) - (c[1]+c[2]+c[3])=4*5 - 10=10.
设sumA表示数组a的前缀和,sumB表示数组b的前缀和,sumC表示数组c的前缀和。
sumA[i]=(i+1)*sumB[i] - sumC[i]。
上述公式其实是可以严格证明的。
证明:
假设要求数组a的前x项和。
因为a[i]=b[1]+b[2]+…+b[i],
a[1]+a[2]+a[3]+…+a[x]=b[1]+(b[1]+b[2])+(b[1]+b[2]+b[3])+…+(b[1]+b[2]+b[3]+…+b[x]).
=b[1]x + b[2](x-1) + b[3](x-2) + b[i](x-i+1) +…+b[1]*x.
所以需要b[x]一次,b[x-1]两次,b[x-2]三次,…,b[i] (x-i+1)次,b[1] x次。
故只需用两个树状数组去维护原数组a的差分数组b的前缀和以及b的i倍数组c的前缀和即可得到原数组a的前缀和。