树状数组 区间更新(利用了伪线段树) + poj3468题解

xiaoxiao2021-02-28  16

废话不多说 先粘代码 以poj3468为例

A Simple Problem with Integers Time Limit: 5000MS Memory Limit: 131072K Total Submissions: 123915 Accepted: 38431 Case Time Limit: 2000MS Description You have N integers, A1, A2, ... , AN. You need to deal with two kinds of operations. One type of operation is to add some given number to each number in a given interval. The other is to ask for the sum of numbers in a given interval. Input The first line contains two numbers N and Q. 1 ≤ N,Q ≤ 100000. The second line contains N numbers, the initial values of A1, A2, ... , AN. -1000000000 ≤ Ai ≤ 1000000000. Each of the next Q lines represents an operation. "C a b c" means adding c to each of Aa, Aa+1, ... , Ab. -10000 ≤ c ≤ 10000. "Q a b" means querying the sum of Aa, Aa+1, ... , Ab. Output You need to answer all Q commands in order. One answer in a line. Sample Input 10 5 1 2 3 4 5 6 7 8 9 10 Q 4 4 Q 1 10 Q 2 4 C 3 6 3 Q 2 4 Sample Output 4 55 9 15 Hint The sums may exceed the range of 32-bit integers. Source POJ Monthly--2007.11.25, Yang Yi //区间更新 #include <iostream> #include <algorithm> #include <cstdio> #include <cmath> #include <cstring> using namespace std; const int N=5e5+10; long long sum[N];//前缀和树状数组 long long a[N]; long long n,m; long long ddd[N];//加数树状数组 long long lowbit(long long t) { return t&(-1*t); } void update(long long x,long long v, long long t[]) { //给x的起始位置加v 并更新k数组 for(int i=x; i<=n; i+=lowbit(i)) { t[i]+=v; } } long long getsum(long long x, long long t[]) { //求k数组第i个前缀和 long long ans=0; for(int i=x; i>0; i-=lowbit(i)) ans+=t[i]; return ans; } void refresh_range(long long i,long long j, long long aa) { //给区间i~j中的所有元素加aa,更新对应的前缀和树状数组和加数树状数组 update(i,-(i-1)*aa,sum); update(i,aa,ddd); update(j+1,j*aa,sum); update(j+1,-1*aa,ddd); } long long range_sum(long long i,long long j) { //求i~j的区间和 long long ans=0; ans+=getsum(j,sum)+getsum(j,ddd)*j; ans-=getsum(i-1,sum)+getsum(i-1,ddd)*(i-1); return ans; } int main() { ios::sync_with_stdio(false); while(cin>>n>>m&&n) { for(int i=1; i<=n; i++) { cin>>a[i]; refresh_range(i,i,a[i]); } char s; for(int i=1;i<=m;i++) { cin>>s; if(s=='C') { long long x,y,z; cin>>x>>y>>z; if(x>y) swap(x,y); refresh_range(x,y,z); } else if(s=='Q') { long long x,y; cin>>x>>y; if(x>y) swap(x,y); cout<<range_sum(x,y)<<endl; } } } return 0; }

我们知道,在单点更新的时候,对一个位置 x 的值加 a 时,位置在 x 之前的所有前缀和都是没有影响的,受影响的是从 x 开始之后的所有位置的前缀和。

利用单点更新和区间查询进行区间更新和单点查询,,在 x < i 和 x > j 的范围内(待更新区间之外),只要区间更新的加数 a 确定,这些前缀和便是固定值,不需要知道单点查询时候的位置,因此这部分工作可以在更新的时候实现,具体如何实现呢?

我们发现 在i <= x <= j 时,前缀和增量 y = (x-i+1)a = xa -(i-1)*a ;在 x > j 时,前缀和增量 y = (j - i +1 )a = ja - (i-1)*a ; 由此可以得出结论,在 x >= i 时,y 的表达式中均包含 -(i-1)a ,所以根据前缀和的规律,我们利用单点更新将此值加在 i 位置的前缀和上,则可以达到给 x >= i 范围内的所有前缀和均加上了此值。在 i <= x <= j 的区间范围内,我们发现前缀和增量表达式 y 除 -(i-1)a 这部分外,剩余的部分为 xa ,x 即为单点查询的位置,这个值不确定,只有在某一次查询的时候才能被确定,因此,这个操作需要在单点查询的时候完成。在 x > j 的范围内,前缀和表达式 y = ja - (i-1)a , 除已经在 x = i 的位置处理的 -(i-1)a 这部分之外,剩余部分为 ja ,这部分值只与区间更新的端点和更新的加数 a 有关,因此可以在更新时候处理,这时候我们需要将 j+1 位置的前缀和由 sum[ j+1 ] 更新为 sum[ j+1] + ja 。至此,除了查询 i <= x <= j 范围的值不正确之外,其余值都能够保证正确性。

当单点查询的位置 x 在 i~j 范围内的时候,我们需要在 x 的位置 单点更新前缀和 sum[ x ] 为 sum[x] +x*a 。现在,我们已经将三个区间都处理结束了,对吗?但是,这是真的结束吗? 大家是否还记得单点更新的操作?操作是这样的:在单点更新的时候,对一个位置 x 的值加 a 时,位置在 x 之前的所有前缀和都是没有影响的,受影响的是从 x 开始之后的所有位置的前缀和。这也就意味着当单点查询的位置 x 在 i~j 范围内的时候,我们对 x 位置的单点更新实际上已经影响到了我们在区间更新时候已经处理好的区间 x > j ,因此我们需要将此处因单点更新加的值 x*a 减去。这也就意味着,我们需要将加数 a 存储起来,否则,等到单点查询的时候,加数 a 都已经变成若干次区间查询后的加数了。但由于,同一个区间的加数多次叠加后,结果还是正确的。因此,我们可以仿照线段树时的 lazy_tag 想法存储每个节点对应的加数(线段树 lazy_tag//相当于ddd数组)。

对于某一个具体的加数该如何存储和维护呢?有了前面的分析思路,我们应该可以用两个树状数组分别存储前缀和与加数,其中一个树状数组 lazy_tag来存储加数,假设某次区间更新的范围为 i~j ,加数为 a ,我们需要做的操作为:

lazy_tag[i] += a ; //给 x >= i 的所有节点加数均加 a

lazy_tag[j+1] -= a ; // 给 x >j 的所有节点均减 a ,消除 i 位置处理的影响

这样,就能保证仅对 i ~j 范围内的加数加了 a ,以此配合区间更新时的操作保证所有前缀和的正确性。此处,对加数的处理和原问题(区间更新)处理思路是一样的,给某一区间的所有加数均加 a 的问题实质就是区间更新的问题。因此此处,某一位置 x 的加数不是 ddd[x] 的值,而是 ddd 树状数组的第 x 位置前缀和。

转载请注明原文地址: https://www.6miu.com/read-2050080.html

最新回复(0)