转自
https://blog.csdn.net/non_cease/article/details/7435052?tdsourcetag=s_pcqq_aiomsg
树状数组F:
一 . 首先,看更新操作update(s, t, d)把区间A[s]…A[t]都增加d,我们引入一个数组delta[i],表示
A[i]…A[n]的共同增量,n是数组的大小。那么update操作可以转化为:
1)令delta[s] = delta[s] + d,表示将A[s]…A[n]同时增加d,但这样A[t+1]…A[n]就多加了d,所以
2)再令delta[t+1] = delta[t+1] - d,表示将A[t+1]…A[n]同时减d
此番操作后,对delta[i]求前缀和,就是单点查询。(此番操作是为了后边求delta[i]对sum[x]的贡献)
二 .解题思路从暴力出发,求区间和,就是求 右端点t的前缀和sum[t]-左端点s-1的前缀和sum[s-1]。
前缀和如何求?
分为俩部分
1、原数组的和。将其存在一个数组A[]中。
2、该区间内累计增量的和。delta[i]对sum[x]的贡献为(x+1-i)delta[i] (自己带俩个数试试,确实如此)。
综上1、2:
sum[x]=A[1]+…A[x]+delta[1]x+delta[2](x-1)+delta[3](x-2)+…delta[x]*1
=A[1]+...A[x]+segma(delta[i]*(x+1-i))
=segmaA[i]+(x+1)*segma(delta[i])-segma(delta[i]*i) ,1<=i<=x。 (该化简的公式下面会用到)
segma就是求前缀和,因此创建三个树状数组,一个是原数组,一个是delta[i],一个是delta[i]*i。
下面就是代码了:
#include<iostream>
#include<string>
using namespace std;
long long a1[100002];//delta[i];
long long a2[100002];//delta[i]*i;
long long tree[100002];
long long a[100002];
int N;
long long lowbit(long long x)
{
return x & -x;
}
void updata(long long *an,long long x, long long k) //单点更新函数
{
for (long long i = x; i <= N; i += lowbit(i)) {
an[i] += k;
}
}
long long getsum(long long *an,long long x) //求前缀和函数
{
long long ans = 0;
for (long long i = x; i > 0; i -= lowbit(i)) {
ans += an[i];
}
return ans;
}
int main()
{
int Q;
long long x, y; //区间左右端点
cin >> N >> Q;
for (int i = 1; i <= N; i++) {
cin >> a[i]; //原数组
updata(tree,i, a[i]); //原数组更新到树状数组
}
while(Q--){
char q;
cin >> q;
cin >> x >> y;
if(q=='Q'){
long long sum = 0; //由上面公式得如下操作
sum += getsum(tree, y) + (y + 1) * getsum(a1, y) - getsum(a2, y);
sum -= getsum(tree, x - 1) + (x - 1 + 1) * getsum(a1, x - 1) - getsum(a2, x - 1);
cout << sum << endl;
}
else if (q == 'C') {
int w;
cin >> w;
updata(a1, x,w ); //俩个为了求端点sum[x]所开的树状数组的更新
updata(a1, y + 1, -w);
updata(a2, x, x * w);
updata(a2, y + 1, -w * (y + 1));
}
}
return 0;
}