一个简单的整数问题2(树状数组版)

一个简单的整数问题2

题目描述

在这里插入图片描述


题目解释

对于第一个操作,意思是区间修改;对于第二个操作,意思是区间查询。但是树状数组只能处理单点修改区间查询。因此,需要做出一些转换。

在这里插入图片描述


核心思路

设a数组是原数组,b数组是a数组的差分数组,tr1是维护b[i]数组的树状数组,tr2是维护i*b[i]的树状数组。因为第二个操作是想要查询区间 [ l , r ] [l,r] [l,r]的和,因此需要前缀和。

即要求出 ∑ i = 1 x a i = a 1 + a 2 + ⋯ + a x \sum \limits _{i=1}^{x}a_i=a_1+a_2+\cdots +a_x i=1xai=a1+a2++ax。又因为b是a数组的差分数组,所以b数组的前缀和就是某个a[i]。因此 ∑ i = 1 x ∑ j = 1 i b [ j ] \sum \limits _{i=1}^{x}\sum \limits _{j=1}^{i}b[j] i=1xj=1ib[j]。如下图所示,蓝色部分就是序列a的前缀和,红色部分是我们填补了每个数后面的差分,此时化简得到: ∑ i = 1 x a [ i ] = ( x + 1 ) ∑ i = 1 x b [ i ] − ∑ i = 1 x i ∗ b [ i ] \sum \limits _{i=1}^{x}a[i]=(x+1)\sum \limits _{i=1}^{x}b[i]-\sum \limits _{i=1}^{x}i*b[i] i=1xa[i]=(x+1)i=1xb[i]i=1xib[i]。表示序列a的前缀和a[1~x],即 a 1 + a 2 + ⋯ + a x a_1+a_2+\cdots+a_x a1+a2++ax。所以,我们就需要两个树状数组,其中tr1用来维护b[i]差分数组,tr2用来维护i*b[i]的差分数组。

本题为什么求 ∑ i = 1 x a i \sum \limits _{i=1}^{x}a_i i=1xai还要那么麻烦,直接用树状数组的getSum(int x)函数求不就可以了嘛?这么麻烦主要是因为,我们第一个区间修改操作,利用了差分数组,把这个区间修改操作成功转换为了单点修改,也就是说第一种操作用到了差分,那么第二种操作要尽量在差分的基础上求出序列a的前缀和,因此才需要这么麻烦,但是二者都是用到了差分的思想,这样时间复杂度就降低了。

如何理解tr1是维护b[i]差分数组的树状数组,tr2是维护i*b[i]差分数组的树状数组呢?

我们知道树状数组有两个经典操作,即单点修改函数update(int x,int v)和区间查询函数getSum(int x)。对于单点修改函数来说,参数x和v的值分别是什么呢?对于树状数组tr1来说,x的值是由差分数组b[i]中的i提供的,v的值是由差分数组b[i]差分出来的值 b = a [ i ] − a [ i − 1 ] b=a[i]-a[i-1] b=a[i]a[i1]提供的,也就是说,tr1的单点修改函数中的两个参数都分别来源于差分数组b[i]。所以称tr1是维护差分数组b[i]数组的树状数组。对于树状数组tr2来说,分析同理。

在这里插入图片描述

怎么看待第一种操作,把区间修改转换成了单点修改呢?

要想在原序列a的区间[l,r]都加上常数d,对于差分数组b[i]来说,只需要b[l]+=d,b[r+1]-=d即可,这样就可以使得序列a在区间[l,r]都加上了常数d。但是对于差分数组i*b[i]来说,就需要 b [ l ] + = l d , b [ r + 1 ] − = l d b[l]+=ld,b[r+1]-=ld b[l]+=ldb[r+1]=ld了。也就是说,在单点修改函数update传入的参数时,差分数组b[i]传入的是d和-d,而差分数组 i × b [ i ] i \times b[i] i×b[i]来说,传入的是ld和-(r+1)d。注意,树状数组tr1的单点修改函数中是tr1[i]+=v,v是ld和-(r+1)d,即tr1[i]+=ld和tr1[i]+=-(r+1)d。

求前缀和i*b[i]时,前面加上ld,后面减去-(r+1)d,是怎么差分抵消的,使得原序列a在区间[l,r]加上了常数d呢?

我们不能这么想,我们要把 ∑ i = 1 x b [ i ] \sum \limits _{i=1}^{x}b[i] i=1xb[i] ( x + 1 ) ∑ i = 1 x i ∗ b [ i ] (x+1)\sum \limits _{i=1}^{x}i*b[i] (x+1)i=1xib[i]放在一起考虑。虽然它俩是相互独立的,但是求序列a的前缀和时,它们的目的都是一致的–都是使得序列a在区间[l,r]都加上常数d。但是sum(tr1,x) × \times ×(x+1)-sum(tr2,x)和他俩不相互独立呀。前面加上l × \times ×d,后面减(r+1) × \times ×d,这个是我们在[l, r]内加上d后,对树状数组tr2的影响。事实上并没有差分抵消掉。但是在求区间和那,sum(tr1,x)*(x+1)-sum(tr2,x)中,差分抵消了。为什么在求区间和那块差分抵消了呢?且看证明:

在这里插入图片描述

因此,在某个位置 1 ≤ t ≤ x 1\leq t\leq x 1tx加上a后,由结果可知,恰是在 1 ≤ t ≤ x 1\leq t\leq x 1tx这段原本的和的基础上增加了 t ( x − a + 1 ) t(x-a+1) t(xa+1)

其实我们不要想着为什么 b [ l ] + = l ∗ d b[l]+=l*d b[l]+=ld b [ r + 1 ] − = ( r + 1 ) ∗ d b[r+1]-=(r+1)*d b[r+1]=(r+1)d为什么能够差分抵消。由公式可知,我们知道 i ∗ b [ i ] i*b[i] ib[i]这个差分数组是 b [ i ] b[i] b[i]这个差分数组的i倍。如下例子所示,假设在区间[2,4]都加上了常数3,看右边,此时差分数组i*b[i]为{1,8,3,4,-10},除以i后,得到差分数组b[i]为{1,4,1,1,-2},把此时的差分数组b[i]对比左边的差分你数组b[i]={1,1,1,1,1},可知此时b[1]=4,而原来b[1]=1,即 b [ 2 ] + = 3 b[2]+=3 b[2]+=3,此时b[5]=-2,而原来b[5]=1,即 b [ 5 ] − = 3 b[5]-=3 b[5]=3。由b[i]差分数组的变化,可以推知原数组a在区间[l,r]=[2,4]内加上了常数3。也就是说,为我们看b[i]差分数组的变化可以推知序列a的变化,b[2]+=3和b[5]-=3,可以使得序列a在区间[2,4]都加上3,是可以差分抵消的。但是我们不要想着 i × b [ i ] i\times b[i] i×b[i]是差分抵消的。

在这里插入图片描述


代码

#include<iostream>
using namespace std;
typedef long long LL;
const int N=100010;
int n,m;
int a[N];	//原数组a
LL tr1[N];	// 维护b[i]的前缀和
LL tr2[N];	// 维护b[i] * i的前缀和
int lowbit(int x)
{
    return x&(-x);
}
void update(LL tr[],int x,LL v)
{
    for(int i=x;i<=n;i+=lowbit(i))
    tr[i]+=v;
}
LL getSum(LL tr[],int x)
{
    LL sum=0;
    for(int i=x;i>0;i-=lowbit(i))
    sum+=tr[i];
    return sum;
}
//计算原序列a的前缀和 即 al+..+ar
LL prefix_sum(int x)
{
    return (x+1)*getSum(tr1,x)-getSum(tr2,x);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)
    {
        int b=a[i]-a[i-1];	//差分得到的值b
        update(tr1,i,b);	//因为tr1维护的差分数组是b[i],将差分得到的值b传给单点修改update函数的v
        update(tr2,i,(LL)i*b);//因为tr2维护的差分数组是i*b[i],因为b[i]差分得到了b,所以i*b[i]应该差分得到i*b,将差分能得到的值i*b传给单点修改update函数的v
    }
    while(m--)
    {
        char op[2];
        int l,r,d;
        scanf("%s%d%d",op,&l,&r);
        //如果是区间查询操作  想要求出序列a在区间[l,r]中的总和
        if(*op=='Q')
        {
            //由s[r]-s[l-1]就可以求出序列a在区间[l,r]中的前缀和了。
            LL Sr=prefix_sum(r);
            LL Sl=prefix_sum(l-1);
            printf("%lld\n",Sr-Sl);
        }
        //如果是区间修改操作 想要序列a的区间[l,r]都加上常数d
        else
        {
            scanf("%d",&d);
            //前面加上d,后面加上-d,这是我们在[l,r]内加上d后,对树状数组tr1的影响。
            update(tr1,l,d);
            update(tr1,r+1,-d);
            //前面加上l*d,后面减(r+1)*d,这个是我们在[l, r]内加上d后,对树状数组tr2的影响
            update(tr2,l,l*d);
            update(tr2,r+1,-(r+1)*d);
        }
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卷心菜不卷Iris

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值