线段树?树状数组!

众所周知,线段树可以实现区间修改+区间查询。但实际上,树状数组也可以,并且在较为一般的情况下常数更小、占用空间更少、码量更小
比较图片
上面为树状数组实现,下面为线段树实现(已经用了lazy tag),可以看到明显的碾压

d[i]d[i]为记录修改的差分数组,那么若我们想查询区间1~p的修改时,我们需要把每个位置的差分数组进行求和,即:
i=1pj=1id[j]\sum_{i=1}^{p}\sum_{j=1}^id[j]

由另一个角度来说,1~p中每个d[i]d[i]会被计算pi+1p-i+1次(从i一直到p,每次都会被计算进去)
所以上式可转化为:
i=1pd[i](pi+1)=i=1pd[i]i+(p+1)i=1pd[i]\sum_{i=1}^{p}d[i]*(p-i+1)=\sum_{i=1}^pd[i]*i+(p+1)*\sum_{i=1}^pd[i]

注意到,ii是一个对数组来说的已知常量,而xx为每次询问的一个变量。所以我们将它们分离后可以将d[i]d[i]d[i]id[i]*i作为两种不同的树状数组来维护

这种分离包含有多个变量的项,使公式中不同变量之间互相独立的思想非常重要
——《算法竞赛进阶指南》

d[i]id[i]*i怎么更新?简单!就跟基本的差分的修改差不多,不过要在修改时乘上当前位置的下标而已。具体请看代码

#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;
}

参考资料:《算法竞赛进阶指南》