【小结】树状数组的区间修改与区间查询

第一次听到树状数组是zsl来讲课QAQ 然后当时他给我们绕了很久 = = 嗯没懂…………结果后来发现这东西实际上是个简单又好用的玩意QAQ

不知道以后的题还会不会用了 因为感觉好像树状数组的题一般都比较简单orz。。


[ 1 ]  树状数组的本职是单点修改+区间查询 维护前缀和 每次修改向上传数据 然后查询区间的时候也是从下往上加值 函数很好写……


修改:

void update(int i, int x)
{
    while(i <= N){
         sum[i] += x;
         i += lowbit(i);
    }
}

查询前缀和:

int Query(int i)
{
    int temp = 0;
    while(x > 0){
         temp += sum[i];
         i -= lowbit(i);
    }
    return temp;
}


[ 2 ]  鉴于树状数组的空间复杂度和时间复杂度都比线段树小 而且代码也短 所以就有大神用强大的脑洞YY出了区间修改+单点查询的树状数组

 = =我的想法是保存原始数列a数组 然后引入delta数组(差分数组) 查询的时候求delta数组的前缀和 再加上原始的a数组就可以了 网上的资料似乎和我的方法不太一样但是……应该也差不多吧……按我的方法的话 代码和上面也差不多 把sum改成delta就行了


[ 3 ]  上面都不是重点……重点是树状数组的区间修改+区间查询 这个很好玩 其实也挺简单

首先依旧是引入delta数组 delta[i]表示区间 [i, n] 的共同增量 于是修改区间 [l, r] 时修改 delta[l] 和 delta[r + 1] 即可(就是差分的思路)

查询的时候是查询区间 [l, r] 的和 即sum[r] - sum[l - 1] 所以现在的问题是求sum[i]


sum[i] = a[1]+...+a[i] + delta[1]*i + delta[2]*(i - 1) + delta[3]*(i - 2)+...+delta[i]*1   // a[i]为原始数组
       = sigma( a[x] ) + sigma( delta[x]  *  (i + 1 - x) )
       = sigma( a[x] ) + (i + 1) * sigma( delta[x] ) - sigma( delta[x] * x )

其中 sigma( a[x] ) 是可以预处理出来的 于是只需要维护 delta[x] 与 delta[x] * x 的前缀和(作为两个树状数组就可以了)

为了试验这个方法我专门去找了之前写线段树挂了好久的例题 = = codevs1082 线段树练习3 

然后交树状数组的代码是 324ms 内存5M过了 线段树是1027ms 13M 如果去掉读入优化的话代码会更短。

/*
作者:Airy
题目:p1082 线段树练习 3
*/

#include <cstdio>
#include <iostream>

#define lowbit(i) (i & (-i))

using namespace std;

int readint()
{
	int sign = 1, n = 0; char c = getchar();
	while(c < '0' || c > '9'){ if(c == '-') sign = -1; c = getchar(); }
	while(c >= '0' && c <= '9') { n = n*10 + c-'0'; c = getchar(); }
	return sign*n;
}

const int Nmax = 200100;

int N, Q;

long long delta[Nmax]; // delta的前缀和 
long long deltai[Nmax]; // delta * i的前缀和 
long long sum[Nmax]; // 原始前缀和

long long Query(long long *array, int pos)
{
	long long temp = 0ll;
	while(pos > 0)
	{
		temp += array[pos];
		pos -= lowbit(pos);
	}
	return temp;
}

void Update(long long *array, int pos, int x)
{
	while(pos <= N)
	{
		array[pos] += x;
		pos += lowbit(pos);
	}
}

int main()
{
	N = readint();
	
	for(int i = 1; i <= N; ++i)
	{
		int x = readint();
		sum[i] = sum[i - 1] + x;
	}
	
	Q = readint();
	
	while(Q--)
	{
		int sign = readint();
		if(sign == 1) // 修改:把[l, r]区间均加上x 
		{
			int l = readint(), r = readint(), x = readint();
			Update(delta, l, x);
			Update(delta, r+1, -x);
			Update(deltai, l, x * l);
			Update(deltai, r+1, -x * (r+1));
		} 
		else // 查询:[l, r]区间和 
		{
			int l = readint(), r = readint(); 
			long long suml = sum[l - 1] + l * Query(delta, l - 1) - Query(deltai, l - 1);
			long long sumr = sum[r] + (r + 1) * Query(delta, r) - Query(deltai, r);
			printf("%lld\n", sumr - suml);
		}
	}
	
	return 0;
}


大概就是这样了。

  • 28
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值