题目描述
给定一个长度为N的数列A,以及M条指令,每条指令可能是以下两种之一:
1、“C l r d”,表示把 A[l],A[l+1],…,A[r] 都加上 d。
2、“Q l r”,表示询问 数列中第 l~r 个数的和。
对于每个询问,输出一个整数表示答案。
输入格式
第一行两个整数N,M。
第二行N个整数A[i]。
接下来M行表示M条指令,每条指令的格式如题目描述所示。
输出格式
对于每个询问,输出一个整数表示答案。
每个答案占一行。
数据范围
$1 \le N,M \le 10^5$,
$|d| \le 10000$,
$|A[i]| \le 1000000000$
样例
输入样例:
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,然后对于每条C操作,我们直接利用前缀和的思想.
- 把b[l]+=d;
- 把b[r+1]-=d;
然后我们就成功达成成就,把维护序列的具体值,转化为维护指令的累计影响,每次操作的影响,在l处开始,然后在r+1处消除.然后就让单点修改可以维护区间修改.
现在b数组的前缀和就是$\sum_{i=1}^x b[i]$ 就是经过指令后a[x]增加的值,那么序列a的前缀和a[1~x]增加的值就是:
$$\sum_{i=1}^x \sum_{j=1}^i b[j] $$
然后上式就可以转换为
$$\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]$$
然后通过上面这个式子,我们就把原来的数组,经过差分操作去维护两个树状数组,一个维护$d_i$,一个维护$d_i \times i$这样的话,我们在区间修改的过程中,就可以在两个树状数组中去查询得到前缀和,然后同理,区间修改操作就是差分数组的修改,每次只需要修改两个点,完美的将区间再次转换为单调修改。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define lowbit(x) x&(-x)
const int N=2e5;
ll c1[N],c2[N],n,m,a[N];
ll add(ll x,ll y)
{
for(ll i=x;i<=n;i+=lowbit(i))
{
c1[i]+=y;
c2[i]+=x*y;
}
}
ll ask(ll x)
{
ll ans=0;
for(ll i=x;i;i-=lowbit(i))
ans+=(x+1)*c1[i]-c2[i];
return ans;
}
int main()
{
scanf("%lld%lld\n",&n,&m);
for(ll i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
add(i,a[i]-a[i-1]);
}
getchar();
while(m--)
{
char ch=getchar();
if (ch=='Q')
{
ll x,y;
scanf("%lld%lld\n",&x,&y);
cout<<ask(y)-ask(x-1)<<endl;
}
if (ch=='C')
{
ll x,y,d;
scanf("%lld%lld%lld\n",&x,&y,&d);
add(x,d);
add(y+1,-d);
}
}
return 0;
}
%%%
为什么不是i*y呀
好好理解上面的公式 函数中的x就是公式中的i 而函数里的i只是枚举用的
orz两个数组既然可以一起操作
码风和我的挺像耶喵
然后通过上面这个式子,我们就把原来的数组,经过差分操作去维护两个树状数组,一个维护di,一个维护di×i这样的话,我们在区间修改的过程中,就可以在两个树状数组中去查询得到前缀和,然后同理,区间修改操作就是差分数组的修改,每次只需要修改两个点,完美的将区间再次转换为单调修改。
不是应该一个维护 b[i] 和一个维护 i*b[i] 吗? hhhh
这两层求和分解得很漂亮
大佬,不是很理解lyd书上前缀和ib[i],前面加ld,后面减(r+1)*d,是怎么抵消的
同问
每次区间修改时,$b[i]$ 数组中只有 $b[l]$ 和 $b[r + 1]$ 发生改变,变化量分别为 $+d$, $-d$
故 $b[i] * i$ 数组中也只有 $b[l] * l$ 和 $b[r + 1] * (r + 1)$ 发生了变化,变化量分别为 $d * l$, $-d * (r + 1)$
我们要的,只不过是 $b[i] * i$ 这个数组的前缀和而已
根本就不用在意前后是否抵消嘛!
这个角度太妙了
di求和的实际意义是每一项的值,所以求和之后区间外的值不变,是必要的。
di’求和之后没有实际意义,就是i * di’的和,r后面的数变和不变,都没有实际意义,不用非要抵消。而每一项定义又是i * di’,所以如果变了,说明求和之后r之后的数就应该变。
大佬,我想 问一下,你的c2数组的修改,你在 l 的位置 加了个l * d,那么这个区间内不就只加了一个d吗?那么,我l + 1的位置就应该加(l + 1) * d,l + 2同理,可是我之后都是加的l * d,这是不是有点问题呀。
不是,是前缀和+l*d.我们这个差分,会差分掉的。
是呀,差分的话,也是从最开始加一直加过去,就这样,(l + 1) 上,不就是加了一个l * d, (l + 2)不也就只加了一个l * d吗,如果按那个公式来理解的话,就是l的位置加上 l * d, (l + 1)的位置加上(l + 1) * d,(l + 2)的位置就是加上(l + 2) * d把,这样推应该没有问题吧
似乎是的。。。。
但是,这样就出现了问题了吧,按道理,(l + 1)的位置不应是加上(l + 1)* d吗,(l + 2)的位置就是加上(l + 2) * d,可是它们都只加了 l * d,这就是我不理解的地方,为什么是这样呢?大佬,求救!!!
我估计今天晚上,或者明天晚上,就会放出树状数组的讲义,到时候,我会着重讲解区间修改。
好的,谢谢大佬%%%
大佬你想通了吗?我也意识到了这个问题,内部的话,会单独差上一个d
但是那个c2指的是d[i]i的前缀和。一个add作用只不过是改变差分数组中一位的值。所以每一位都加上ld并没有问题啊。
大佬,我运行了你的代码发现有点问题。就是读完数据之后,最后一个结果不会输出啊
不会输出吗?QwQ
是啊。我用的VS,莫非是我编译器的问题?
编译器的锅吧