直接放题目,然后再解释:
树状数组:
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;
![2 l r:输出∑ri=la[i]的值;](https://i-blog.csdnimg.cn/blog_migrate/90c1e2da2db8391415478383c563c17f.png)
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。实际上就是对这个数组进行前缀和求某个区间的操作。
以上只是鄙人的拙见,如果有错误、不足之处,还请指正。
这篇博客介绍了树状数组(也称为二进制指数树)在处理单点修改和区间查询问题上的应用。文章详细解析了如何通过差分数组和前缀和的概念来实现区间修改和区间查询,并给出了AC代码示例。关键在于维护两个数组,一个用于存储原始数值的累加和,另一个存储累加和的累计乘积,从而在O(logn)的时间复杂度内完成操作。
744

被折叠的 条评论
为什么被折叠?



