看完之前那篇树状数组入门(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]既为d[i]的前缀和,所以查询某个元素的值,只要查询差分数组中对应位置的前缀和即可;
附上一道例题:(zcmu 1156)
#include
#include
#include
#include
#include
#include
#include
#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
下一个问题:
在一个【1,1000000000】的区间上改变n次【x,y】(n个位置)的值,并且查询m次【a,b】上的区间和;
朴素的方法厉遍的时间复杂度是O(N^2);TLE~~~
用之前提到的“差分”思想,构造前缀和来进行区间的更新和查询;
由公式
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])
所以要维护两个数状数组,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
#include
#include
#include
#include
#include
#include
#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;
}