数组数组区间修改+区间查询

数组数组区间修改+区间查询

在之前整理树状数组笔记时,已经将单点修改以及区间查询写的很清楚了。树状数组本质上就是一个可以在线 快速查询前缀和,并可以快速更新数值并维护的数据结构。我们喜欢用树状数组是因为其代码简单易记,出错率低,并且效率还不错。

理论推导

如果涉及单点修改、区间查询,那么代码很简单。但是如果是区间修改,就需要做一些改变了。首先进行如下推导:(前置知识:前缀和、差分数组)
假设数组a是存放初始数据,sum存放a数组的前缀和,而b数组为差分数组,即每当为[l,r]位置所有元素+val时,b[l]+= val,b[r] -= val。而sumb为b数组的前缀和数组,这样sumb[i]就存放着a[i]修改值。那么很显然最后a[i]的值为a[i]+sumb[i],即初始值+修改值。此时再来考察a数组的前缀和,sum[i]存放的是a[1]+a[2]+…+a[i],(请注意a数组始终未作改变,其改变的值存放在sumb数组中),很显然我们要求的结果(数组a的最终前缀和)
a n s = s u m [ i ] + ∑ j = 1 i s u m b [ j ] ans = sum[ i ]+\sum_{j = 1}^i sumb[ j ] ans=sum[i]+j=1isumb[j]
又由于
s u m b [ i ] = ∑ j = 1 i b [ j ] sumb[i] = \sum_{ j = 1}^i b[j] sumb[i]=j=1ib[j]
所以
a n s = s u m [ i ] + ∑ j = 1 i ∑ k = 1 j b [ k ] ans = sum[i] + \sum_{j = 1}^i \sum_{k = 1}^j b[k] ans=sum[i]+j=1ik=1jb[k]

至此我们已经抛弃了sumb这个保存b数组前缀和的数组,ok吗?我们得到的公式是根据一个常量数组sum和一个不断被修改的b数组来求ans的。(注:这里ans为a数组的最终前缀和)
如果上面的公式没有问题,接下来我们的目标就是求b数组的二重和式。 来变形:
∑ j = 1 i ∑ k = 1 j b [ k ] = ∑ j = 1 i ( i − j + 1 ) ∗ b [ j ] = ( i + 1 ) ∑ j = 1 i b [ j ] − ∑ j = 1 i j ∗ b [ j ] \sum_{j = 1}^i \sum_{k = 1}^j b[k] = \sum_{j = 1}^i (i - j + 1)* b[j] = (i +1)\sum_{j = 1}^ib[j] - \sum_{j = 1}^i j* b[j] j=1ik=1jb[k]=j=1i(ij+1)b[j]=(i+1)j=1ib[j]j=1ijb[j]
首先,这个公式变换是常用套路,如果有问题可以自己用矩阵求和来推一下。其次,变换成这个公式的好处是我们只需维护 j * b[j] 的前缀和就可以直接求出a数组修改后的前缀和了。

具体实现:

具体操作是,我们建立两个树状数组c0和c1。其中c0是用来求b[j]前缀和的,而c1是用来求j * b[j]前缀和的。那么对于每个区间修改(为[l,r]上所有数字+val),我们执行4个操作:

  1. 在树状数组c0中,把位置l上的数 + val。
  2. 在树状数组c0中,把位置r+1上的数 - val。
  3. 在树状数组c1中,把位置l上的数 + l* val。
  4. 在树状数组c1中,把位置r+1上的数 -(r+1)* val

另外,sum数组存放序列a的原始前缀和。所以每次查询[l,r]前缀和,当然还是拆分成求[1,r]和[1,l-1]的前缀和,二者相减。写成式子如下:
( s u m [ r ] + ( r + 1 ) ∗ a s k ( c 0 , r ) − a s k ( c 1 , r ) ) − ( s u m [ l − 1 ] + l ∗ a s k ( c 1 , l − 1 ) − a s k ( c 1 , l − 1 ) ) (sum[r] + (r+1) * ask(c0,r) - ask(c1,r) ) - (sum[l-1] + l * ask(c1,l-1) - ask(c1,l-1) ) (sum[r]+(r+1)ask(c0,r)ask(c1,r))(sum[l1]+lask(c1,l1)ask(c1,l1))

代码示例:

这里以POJ3468为例:

#include
const int MAXN = 1e5+10;
typedef long long ll;
ll c[2][MAXN];
ll a[MAXN],sum[MAXN];
int n,m;

void add(int k,int x,int y){
	for(;x <= n;x += x&-x) c[k][x] += y;
}
ll ask(int k,int x){
	ll ans = 0;
	for(;x;x -= x&-x) ans += c[k][x];
	return ans;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i = 1;i <= n;i++) scanf("%lld",a+i);
	for(int i = 1;i <= n;i++) sum[i] += sum[i-1] + a[i];
	while(m--){
		char op[10];
		int l,r,val;
		scanf("%s",op);
		if(op[0] == 'C'){
			scanf("%d%d%d",&l,&r,&val);
			add(0,l,val);
			add(0,r+1,-val);
			add(1,l,l*val);
			add(1,r+1,-(r+1)*val);
		} else{
			scanf("%d%d",&l,&r);
			ll ans = sum[r] + (r+1)*ask(0,r) - ask(1,r);
			ans -= sum[l-1] + l*ask(0,l-1) - ask(1,l-1);
			printf("%lld\n",ans);
		}
	}
	return 0;
}

参考书目:

  • 《算法竞赛进阶指南》.李煜东.P200

你可能感兴趣的:(数据结构)