树状数组[区间修改,区间查询]

也许更好的阅读体验

好东西,以后可以不打线段树了

本篇假定读者都会最基础的两种树状数组,即区改单查和单改区查

思考如何维护一个区间的值,想到了差分
对一个差分数组做一次前缀和可以得到每个位置的值
再对每个位置累加一下就是一个区间的值
公式化的讲,就是
设差分数组为 c c c
则每个位置的值
v a l i = ∑ j = 1 i c j val_i=\sum\limits_{j=1}^ic_j vali=j=1icj
一个区间 [ l , r ] [l,r] [l,r]的值
s l , r = ∑ i = l r v a l i s_{l,r}=\sum\limits_{i=l}^rval_i sl,r=i=lrvali
写成前缀和相减的形式就是
s l , r = ∑ i = 1 r v a l i − ∑ i = 1 l − 1 v a l i s_{l,r}=\sum\limits_{i=1}^rval_i-\sum\limits_{i=1}^{l-1}val_i sl,r=i=1rvalii=1l1vali

不难发现,一个区间的值实际上就是差分数组前缀和的前缀和做减法

也就是说,我们只要维护出前缀和的前缀和就可以用树状数组维护区间了
考虑如何维护前缀和的前缀和
s p = ∑ i = 1 p ∑ j = 1 i c j s_p=\sum\limits_{i=1}^p\sum\limits_{j=1}^ic_j sp=i=1pj=1icj
考虑每个 c j c_j cj的出现次数,可以得到
s p = ∑ i = 1 p ( p − i + 1 ) c i = ( p + 1 ) ∑ i = 1 p c i − ∑ i = 1 p i ∗ c i s_p=\sum\limits_{i=1}^p\left(p-i+1\right)c_i=\left(p+1\right)\sum\limits_{i=1}^pc_i-\sum\limits_{i=1}^pi *c_i sp=i=1p(pi+1)ci=(p+1)i=1pcii=1pici

经过如上的简单推导,我们只要维护 ∑ i = 1 p c i \sum\limits_{i=1}^pc_i i=1pci ∑ i = 1 p i ∗ c i \sum\limits_{i=1}^pi *c_i i=1pici这两个东西就可以了
前者就是差分数组,而后者我们只要在维护差分数组时乘以相应的位置的下标即可
这两个东西我们都可以用树状数组维护单点修改区间查询一样的方法维护

int c1[maxn],c2[maxn];
int lbt (int x){ return x & -x; }
void modify (int l,int r,int v)//维护差分数组
{
	++r;
	for (int i=l;i<=n;i+=lbt(i))	c1[i]+=v,c2[i]+=l*v;
	for (int i=r;i<=n;i+=lbt(i))	c1[i]-=v,c2[i]-=r*v;
}

最开始这两句我没有看懂,不是说好的维护 i ∗ c i i*c_i ici吗,怎么写出来时就变成 l ∗ v l*v lv r ∗ v r*v rv
如果你也有这样的疑惑,这说明太久没打树状数组或者没想树状数组的原理你也忘记树状数组的原理了

这个东西维护的是差分数组,而不是差分数组的前缀和!

对于查询

int query (int l,int r)
{
	int res=0;
	for (int i=r;i;i-=lbt(i))	res+=(r+1)*c1[i]-c2[i];
	for (int i=l-1;i;i-=lbt(i))	res-=l*c1[i]-c2[i];
	return res;
}

如有哪里讲得不是很明白或是有错误,欢迎指正
如您喜欢的话不妨点个赞收藏一下吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值