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

废话不多说 先粘代码 以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 位置前缀和。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值