众所周知,线段树可以实现区间修改+区间查询。但实际上,树状数组也可以,并且在较为一般的情况下常数更小、占用空间更少、码量更小
上面为树状数组实现,下面为线段树实现(已经用了lazy tag),可以看到明显的碾压
设为记录修改的差分数组,那么若我们想查询区间1~p的修改时,我们需要把每个位置的差分数组进行求和,即:
由另一个角度来说,1~p中每个会被计算次(从i一直到p,每次都会被计算进去)
所以上式可转化为:
注意到,是一个对数组来说的已知常量,而为每次询问的一个变量。所以我们将它们分离后可以将与作为两种不同的树状数组来维护
这种分离包含有多个变量的项,使公式中不同变量之间互相独立的思想非常重要
——《算法竞赛进阶指南》
怎么更新?简单!就跟基本的差分的修改差不多,不过要在修改时乘上当前位置的下标而已。具体请看代码
#include <cstdio>
const int maxn=1e5+10;
typedef long long ll;
ll d[2][maxn],s[maxn];//d[1]即为保存d[i]*i的数组,s为前缀和数组
int n,m;
int lowbit(int x) {return x&(-x);}
void init()
{
for (int i=1;i<=n;i++)
{
scanf("%lld",&s[i]);
s[i]+=s[i-1];
}
}
void change(int t,int p,ll del)
{
while(p<=n)
{
d[t][p]+=del;
p+=lowbit(p);
}
}
ll ask(ll *a,int p)
{
ll res=0;
while(p)
{
res+=a[p];
p-=lowbit(p);
}
return res;
}
ll ask(int p)
{
return s[p]+ask(d[0], p)*(p+1)-ask(d[1], p);
}
int main()
{
scanf("%d%d",&n,&m);
init();
for (int i=1;i<=m;i++)
{
int op;
scanf("%d",&op);
if (op==1)
{
int l,r,d;
scanf("%d%d%d",&l,&r,&d);
change(0, l, d),change(0, r+1, -d);
change(1, l, (ll)l*d),change(1, r+1, -(ll)(r+1)*d);
}
else
{
int l,r;
scanf("%d%d",&l,&r);
printf("%lld\n",ask(r)-ask(l-1));
}
}
return 0;
}
参考资料:《算法竞赛进阶指南》