算法竞赛进阶指南 0X40数据结构进阶——一个简单的整数问题2

题目链接

AcWing 243. 一个简单的整数问题2 hard

题目描述

给定一个长度为 N N N 的数列 A A A,以及 M M M 条指令,每条指令可能是以下两种之一:

  • C l r d,表示把 A [ l ] , A [ l + 1 ] , … , A [ r ] A[l],A[l+1],…,A[r] A[l],A[l+1],,A[r] 都加上 d d d
  • Q l r,表示询问数列中第 l ∼ r l∼r lr 个数的和。

对于每个询问,输出一个整数表示答案。

输入格式

第一行两个整数 N , M N,M N,M

第二行 N N N 个整数 A [ i ] A[i] A[i]

接下来 M M M 行表示 M M M 条指令,每条指令的格式如题目描述所示。

输出格式

对于每个询问,输出一个整数表示答案。

每个答案占一行。

数据范围
  • 1 ≤ N , M ≤ 1 0 5 1≤N,M≤10^5 1N,M105
  • ∣ d ∣ ≤ 10000 |d|≤10000 d10000
  • ∣ A [ i ] ∣ ≤ 1 0 9 |A[i]|≤10^9 A[i]109
输入样例:

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

输出样例:

4
55
9
15

解法:树状数组

一个简单的整数问题

这道题的数组 b b b 是原数组 a a a 的差分数组。
即:

  • a [ 1 ] a[1] a[1] 的增量为 b [ 1 ] b[1] b[1]
  • a [ 2 ] a[2] a[2] 的增量为 b [ 1 ] + b [ 2 ] b[1] + b[2] b[1]+b[2]
  • a [ 3 ] a[3] a[3] 的增量为 b [ 1 ] + b [ 2 ] + b [ 3 ] b[1] + b[2] + b[3] b[1]+b[2]+b[3]
  • a [ 4 ] a[4] a[4] 的增量为 b [ 1 ] + b [ 2 ] + b [ 3 ] + b [ 4 ] b[1] + b[2] + b[3] + b[4] b[1]+b[2]+b[3]+b[4]
  • a [ x ] a[x] a[x] 的增量为 b [ 1 ] + b [ 2 ] + b [ 3 ] + b [ 4 ] + . . . + b [ x ] b[1] + b[2] + b[3] + b[4] + ...+b[x] b[1]+b[2]+b[3]+b[4]+...+b[x]

那么对于数组 a a a 的前缀和 a [ 1 ∼ x ] a[1 \sim x] a[1x] 整体的增量就是:

b [ 1 ] + ( b [ 1 ] + b [ 2 ] ) + ( b [ 1 ] + b [ 2 ] + b [ 3 ] ) + ( b [ 1 ] + b [ 2 ] + b [ 3 ] + b [ 4 ] ) + . . . + ( b [ 1 ] + b [ 2 ] + b [ 3 ] + b [ 4 ] . . . + b [ x ] ) b[1] + (b[1] + b[2]) + (b[1] + b[2] + b[3]) + (b[1] + b[2] + b[3] + b[4]) + ... + (b[1] + b[2] + b[3] + b[4]...+b[x]) b[1]+(b[1]+b[2])+(b[1]+b[2]+b[3])+(b[1]+b[2]+b[3]+b[4])+...+(b[1]+b[2]+b[3]+b[4]...+b[x])

∑ i = 1 x ∑ j = 1 i b [ j ] \sum_{i = 1}^{x}\sum_{j=1}^{i} b[j] i=1xj=1ib[j]

上式我们可以稍微将其转换一下形式:

∑ i = 1 x ∑ j = 1 i b [ j ] = ∑ i = 1 x ( x − i + 1 ) × b [ i ] = ( x + 1 ) ∑ i = 1 x b [ i ] − ∑ i = 1 x i × b [ i ] \sum_{i = 1}^{x}\sum_{j=1}^{i} b[j] = \sum_{i = 1}^{x}(x - i + 1) \times b[i] = (x + 1)\sum_{i = 1}^{x}b[i] - \sum_{i = 1}^{x}i \times b[i] i=1xj=1ib[j]=i=1x(xi+1)×b[i]=(x+1)i=1xb[i]i=1xi×b[i]

我们使用两个树状数组 c 0 c_0 c0 c 1 c_1 c1,开始时将它们初始化为 0 0 0。我们用 c 0 c_0 c0 来维护 b [ i ] b[i] b[i] 的前缀和 ;用 c 1 c_1 c1 来维护 i × b [ i ] i \times b[i] i×b[i] 的前缀和。

对于求区间和的操作 C l r d

  • c 0 c_0 c0 中,把位置 l l l 上的数加上 d d d
  • c 0 c_0 c0 中,把位置 r + 1 r + 1 r+1 上的数加上 − d -d d
  • c 1 c_1 c1 中,把位置 l l l 上的数加上 l × d l \times d l×d
  • c 1 c_1 c1 中,把位置 r + 1 r + 1 r+1 上的数加上 − ( r + 1 ) × d -(r + 1) \times d (r+1)×d

另外我们建立一个 a a a 的前缀和数组 s u m sum sum

那么对于每一条查询指令 Q l r

我们最终的答案为:

  • 区间 [ 1 , r ] [1,r] [1,r] 的和为 s 1 = s u m [ r ] + ( r + 1 ) × q u e r y ( c 0 , r ) − q u e r y ( c 1 , r ) s1 = sum[r] + (r + 1) \times query(c_0,r) - query(c_1,r) s1=sum[r]+(r+1)×query(c0,r)query(c1,r)
  • 区间 [ 1 , l − 1 ] [1,l - 1] [1,l1] 的和为 s 2 = s u m [ l − 1 ] + l × q u e r y ( c 0 , l − 1 ) − q u e r y ( c 1 , l − 1 ) s2 = sum[l - 1] + l \times query(c_0,l - 1) - query(c_1,l-1) s2=sum[l1]+l×query(c0,l1)query(c1,l1)
  • 那么最终的答案,区间 [ l , r ] [l,r] [l,r] 的和为 a n s = s 1 − s 2 ans = s1 - s2 ans=s1s2

时间复杂度: O ( n × l o g n ) O(n \times logn) O(n×logn)

C++代码:

#include<iostream>

using namespace std;

const int N = 1e5+10;

using LL = long long;


//c[0] 表示原始数组 a 的差分数组 b[i] 的树状数组
//c[1] 表示原始数组的差分数组 b[i] * i 的树状数组
int a[N];

LL sum[N], c[2][N];

int n,m;

int lowbit(int x){
    return x & (-x);
}

LL query(int t,int x){
    LL ans = 0;
    for(;x;x -= lowbit(x)) ans += c[t][x];
    return ans;
}

void add(int t,int x,LL y){
    for(;x <= n;x += lowbit(x)) c[t][x] += y;
}


int main(){
    cin>>n>>m;
    
    for(int i = 1;i <= n;i++){
         scanf("%d" , &a[i]);
         sum[i] = sum[i - 1] + a[i];
    }

    while(m--){
        char op;
        cin>>op;
        
        int l ,r;
        scanf("%d%d",&l,&r);
        
        if(op == 'C'){
            int d;
            scanf("%d",&d);
            
            add(0 , l , d);
            add(0 , r + 1 , -d);
            
            add(1 , l , l * 1LL * d);
            add(1 , r + 1 , -(r + 1) * 1LL * d);
        }
        else{
            
            LL ans = sum[r] + (r + 1) * 1LL * query(0 , r) - query(1 , r);
            ans -= sum[l - 1] + l * 1LL * query(0 , l - 1) - query(1 , l - 1);
            cout<<ans<<'\n';
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值