树状数组进阶(区间更新+单点查询,区间更新+区间查询)

看完之前那篇树状数组入门(https://blog.csdn.net/qq_39562952/article/details/81296502)相信大家对树状数组有了一些了解,这次我们来看更加深一层次的树状数组应用;

问题引入:

       在一个【1,1000000000】的区间上改变n次【x,y】(n个位置)的值,并且查询z位置上的元素值;

       朴素的方法厉遍的时间复杂度还是O(N^2);TLE~~~

先引入一个“差分”的概念:c【i】=a【i】-a【i-1】(c为差分数组,a为原数组)

更新:

再看问题,我们要更新的是【x,y】这个区间的值(+5),根据上面差分数组的定义,既为c【x...n】+5,c【y+1....n】-5(因为c数组存的是差分,所以a[x]和a[x-1]的差多了5,同理a[r]和a[r+1]的差少了5)用树状数组更新c【x...n】,c【y+1....n】的时间复杂度为O(logn);

查询:

a[i]=$\sum_{j=1}^{i} c[j]$,得到a[i]既为d[i]的前缀和,所以查询某个元素的值,只要查询差分数组中对应位置的前缀和即可;

附上一道例题:(zcmu 1156)

#include <iostream>
#include<algorithm>
#include<cmath>
#include<math.h>
#include<stdio.h>
#include<string>
#include<cstring>
#define maxn  1000000+10
#define pi acos(-1.0)
#define esp 1e-7
typedef long long ll;
using namespace std;
/*树状数组区间更新+单点查询*/
int c[maxn];//树状数组(差分数组)
int a[maxn];//原来的普通数组
int n;//数组的长度
void update(int x, int val)//单点更新
{
	for (; x <= n; x += (x&-x))//更新c数组的中关联的父节点
	{
		c[x] += val;
	}
}
void range_update(int l, int r, int val)//区间更新
{
	update(l, val);//把l之后所有的点都更新一遍
	update(r + 1, -val);//因为r之后的点是不用更新的,但是多更新了,所以要每个点都-val;
}
int ask(int x)//a[x]的值即为差分数组中的前缀和
{
	int res = 0;
	for (; x >= 1; x -= (x&-x))
	{
		res += c[x];
	}
	return res;
}
int main()
{
	int m;//操作次数
	while (cin >> n >> m)
	{
		memset(a, 0, sizeof(a));
		memset(c, 0, sizeof(c));//由于题目中原数组(既彩灯状态的集合)都是0,所以差分也都是0
		int flag, l, r;
		for (int i = 0; i < m; i++)
		{
			cin >> flag >> l >> r;
			if (l > r)//坑点,输入的a,b不一定是a<b的
				swap(l, r);
			if (flag == 0)
			{
				for (int j = l; j <= r; j++)
				{
					if (ask(j) & 1)
						cout << "1";
					else
						cout << "0";
				}
				cout << endl;
			}
			else
			{
				range_update(l, r, 1);
			}
		}

	}
	//system("pause");

	return 0;


}

下一个问题:

问题引入:

       在一个【1,1000000000】的区间上改变n次【x,y】(n个位置)的值,并且查询m次【a,b】上的区间和;

       朴素的方法厉遍的时间复杂度是O(N^2);TLE~~~

用之前提到的“差分”思想,构造前缀和来进行区间的更新和查询;

                                            由公式$\sum_{i=1}^{n} a[i]$=$\sum_{i=1}^{n}\sum_{j=1}^ic[j]$

a[1]+a[2]+...+a[n]

= (c[1]) + (c[1]+c[2]) + ... + (c[1]+c[2]+...+c[n]) 

= n*c[1] + (n-1)*c[2] +... +c[n]

= n * (c[1]+c[2]+...+c[n]) - (0*c[1]+1*c[2]+...+(n-1)*c[n]) 

 

$\sum_{i=1}^{n} a[i]$=$n*\sum_{i=1}^{n}c[i]-\sum_{i=1}^{n}(c[i]*(i-1))$

所以要维护两个数状数组,sum1[i]=c[i],sum2[i]=c[i]*(i-1);

更新:

sum1的更新跟上个问题一样,都是加上x;

sum2的更新则是加上x*(i-1);(因为第二个数组维护的是c[i]*(i-1),原来c[i]的地方变成了(c[i]+x),所以原式(c[i]*(i-1),

变成c[i]*(i-1)+x*(i-1),既增量为x*(i-1));

查询:

位置n的前缀和即: (n) * sum1数组中n的前缀和 - sum2数组中n的前缀和。

区间[l, r]的和即:位置r的前缀和 - 位置l的前缀和。

附上测试代码:

#include <iostream>
#include<algorithm>
#include<cmath>
#include<math.h>
#include<stdio.h>
#include<string>
#include<cstring>
#define maxn  1000000+10
#define pi acos(-1.0)
#define esp 1e-7
typedef long long ll;
using namespace std;
/*树状数组区间更新+区间查询*/
/*a[1]+...+a[n]=(n+1)(c[1]+...+c[n])- (1*c[1]+2*c[2]+...+n(c[n])) */
int a[maxn] = { 0,1,2,3,4,5 };
int n;
int sum1[maxn];//(c[1]+...+c[n])
int sum2[maxn];//(1*c[1]+2*c[2]+...+n(c[n]))
void update(int x, int val)
{
	for (int i = x; i <= n; i += (i&-i))//更新后缀的父节点
	{
		sum1[i] += val;
		sum2[i] += val * (x-1);
	}
}
void range_update(int l, int r,int val)
{
	update(l, val);
	update(r + 1, -val);
}
int ask(int x)
{
	int res = 0;
	for (int i = x; i >= 1; i-=(i&-i))//求前缀和
	{
		res += x * sum1[i] - sum2[i];
	}
	return res;
}
int range_ask(int l, int r)
{
	return ask(r) - ask(l - 1);
}

int main()
{
	n = 5;
	for (int i = 1; i <= n; i++)
	{
		update(i, a[i] - a[i - 1]);//初始化两个树状数组
	}
	int flag, l, r;
	while (cin >> flag >> l >> r)
	{
		if (flag)
		{
			range_update(l, r, 1);
		}
		else
		{
			cout << range_ask(l, r);
		}
	}
	
	
	
	
	
	
	system("pause");
	return 0;
}

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值