线段树?树状数组!

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

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

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

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

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

d [ i ] ∗ i d[i]*i d[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;
}

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值