一个简单的整数问题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=1∑xai=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=1∑xj=1∑ib[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=1∑xa[i]=(x+1)i=1∑xb[i]−i=1∑xi∗b[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=1∑xai还要那么麻烦,直接用树状数组的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[i−1]提供的,也就是说,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]+=ld,b[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=1∑xb[i]和 ( x + 1 ) ∑ i = 1 x i ∗ b [ i ] (x+1)\sum \limits _{i=1}^{x}i*b[i] (x+1)i=1∑xi∗b[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 1≤t≤x加上a后,由结果可知,恰是在 1 ≤ t ≤ x 1\leq t\leq x 1≤t≤x这段原本的和的基础上增加了 t ( x − a + 1 ) t(x-a+1) t(x−a+1)。
其实我们不要想着为什么 b [ l ] + = l ∗ d b[l]+=l*d b[l]+=l∗d和 b [ r + 1 ] − = ( r + 1 ) ∗ d b[r+1]-=(r+1)*d b[r+1]−=(r+1)∗d为什么能够差分抵消。由公式可知,我们知道 i ∗ b [ i ] i*b[i] i∗b[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;
}