直接放题目,然后再解释:
树状数组:
1.单点修改,区间查询
2.区间修改,单点查询
3.区间修改,区间查询
区间修改,区间查询
Description
给定数列 a[1],a[2],…,a[n],你需要依次进行q个操作,操作有两类:
1 l r x:给定l,r,x,对于所有的i∈[l,r],将a[i]加上x(换言之,将a[l],a[l+1],…,a[r] 分别加上x)
2 l r:给定l,r,求∑ri=la[i]的值(换言之,求a[l]+a[l+1]+…+a[r]的值)
Input
第一行包含2个正整数n,q,表示数列长度和询问个数。保证1≤n,q≤106;
第二行n个整数a[1],a[2],…,a[n],表示初始数列。保证|a[i]|≤106。
接下来q行,每行一个操作,为以下两种之一:
1 l r x:对于所有的i∈[l,r],将a[i]加上x;
Output
对于每个 2 l r 操作,输出一行,每行有一个整数,表示所求的结果。
Samples
Input
5 10
2 6 6 1 1
2 1 4
1 2 5 10
2 1 3
2 2 3
1 2 2 8
1 2 3 7
1 4 4 10
2 1 2
1 4 5 6
2 3 4
Output
15
34
32
33
50
Hint
对于所有数据,1≤n,q≤106,|a[i]|≤106 ,1≤l≤r≤n,|x[i]|≤106 。
AC code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
inline int read() {char ch = getchar(); int x = 0, f = 1;while(ch < '0' || ch > '9') {if(ch == '-') f = -1;ch = getchar();} while('0' <= ch && ch <= '9') {x = x * 10 + ch - '0';ch = getchar();} return x * f;}
ll n,q;
ll a,b,v;
ll k,now,last;
ll sum1,sum2;
ll c1[1000005];
ll c2[1000005];
ll lowbit(ll x) {return x&(-x);}
void add(ll *t,ll x,ll w)
{
while(x<=n)
{
t[x]+=w;
x += lowbit(x);
}
return ;
}
ll sum(ll *t,ll x)
{
ll s=0;
while(x>0)
{
s+=t[x];
x -= lowbit(x);
}
return s;
}
int main()
{
n=read();
q=read();
for(ll i=1;i<=n;i++)
{
now = read();
add(c1,i,now-last);
add(c2,i,(i-1)*(now-last));
last = now;
}
while(q--)
{
k =read();
if(k==1)
{
a=read(),b=read(),v=read();
add(c1,a,v);add(c1,b+1,-v);
add(c2,a,v*(a-1));add(c2,b+1,-v*b);
}
if(k==2)
{
a = read(),b = read();
sum1=(a-1)*sum(c1,a-1)-sum(c2,a-1);
sum2=b*sum(c1,b)-sum(c2,b);
printf("%lld\n",sum2-sum1);
}
}
return 0;
}
这一个就比较麻烦了……
这里会发现我们的add函数和sum函数和之前有所不同,多了一个*t是因为我们要使用多个数组才能实现区间查询的操作,具体如下:
区间修改还是和之前一样,通过差分数组来实现,我们要做的就是实现区间查询。
我们知道sum函数得到的是前缀和也就是说:
a[1]+a[2]+……+a[n]
=(c[1])+(c[1]+c[2])+……+(c[1]+c[2]+……+c[n])
=n*c[1]+(n-1)*c[2]+……+c[n]
=n *(c[1]+c[2]+……+c[n])-(0 *c[1]+1 *c[2]+…+(n-1)*c[n])
最终我们就得到了这个至关重要的式子
n *(c[1]+c[2]+……+c[n])-(0 *c[1]+1 *c[2]+…+(n-1)*c[n])
值得注意的是
因为c[]数组是差分数组,所以a[n] = c[1] + c[2] +……+c[n]。
我们最终需要得到的是 a[n]也就是原数组,而不是差分数组!!
在区间修改,单点查询中,我们最终得到单点只需要sum(n)也是因为 a[n] = c[1] + c[2] +……+c[n]。
★sum(n) = c[1] + c[2] +……+ c[n]。
最初我就是在这里产生了疑惑,因为理解成了a[]数组。
然后我们返回看这个关键点:
n *(c[1]+c[2]+……+c[n])-(0 *c[1]+1 *c[2]+...+(n-1)*c[n])
观察这个式子我们其实只需要 开两个数组,一个表示(c[1]+c[2]+……+c[n]),另一个表示(0 *c[1]+1 *c[2]+…+(n-1)*c[n]) 就可以了。
for(ll i=1;i<=n;i++)
{
now = read();
add(c1,i,now-last);
add(c2,i,(i-1)*(now-last));
last = now;
}
c1数组表示第一个差分数组,add数组的使用和区间修改,单点查询是一样的。观察c2数组就是表示(0 *c[1]+1 *c[2]+…+(n-1)*c[n]) 所以我们只要在输入的时候,再×(i-1)就可以得到这个差分数组了。
在后面添加数的时候也要注意这一点
a=read(),b=read(),v=read();
add(c1,a,v);add(c1,b+1,-v);
add(c2,a,v*(a-1));add(c2,b+1,-v*b);
再数前面乘上位置-1。其他的操作和区间修改,单点查询是一样的。
a = read(),b = read();
sum1=(a-1)*sum(c1,a-1)-sum(c2,a-1);
sum2=b*sum(c1,b)-sum(c2,b);
printf("%lld\n",sum2-sum1);
最后我们只要按照之前的式子对sum进行操作就好了,操作一次我们得到的是a[1]+a[2]+……+a[n],很显然如果是要求[3,5]是不可以的,所以我们添加了另一个求a[1]+a[2]的sum2。实际上就是对这个数组进行前缀和求某个区间的操作。
以上只是鄙人的拙见,如果有错误、不足之处,还请指正。