树状数组总结

树状数组

简介

在这里插入图片描述
用大的节点表示小的节点的信息,在操作的时候可以通过某种方式快速得到小节点的信息。上图中吧原数组a1~a8划分成了若干大块的c数组来维护。如何分块?这里就引入了二进制,能够体会到其精妙。

具体实现

c数组存的是某一段区间的和
如何构建c数组?又是如何c数组快速求出区间的和呢?

lowbit

这个操作可以得到数字二进制下最低位1的位置
例如
lowbit(111)—> 001
lowbit(10100) —> 00100

int lowbit(int x)
{
	return x & (-x);
}

构建

数组 c[i] = a[i - lowbit(i) ] + 1 ~ a[i]

举例:
n = (1010101) 2进制
Cx = a[1010101] ~ a[1010101]
Cy = a[1010001] ~ a[1010100]
Cz = a[1000001] ~ a[1010000]
Cq = a[0000001] ~ a[1000000]
从下往上看的话正好区间完全拼接为1~1010101

查询

利用以上二进制的性质进行快速的区间前缀和查询
例如查询1~x,那么就从大到小依次累加C数组的值(这个操作在循环中一直lowbit就可以)
如果查询5~x 那么就query(x) - query(4)即可
上代码:
结合上边的图会更加清楚

int query(int x)
{
	int res = 0;
	//例如求前六个数的前缀和
	//通过x-lowbit(x)我们只需要累加c(4)和c(6)即可
	for(;x;x-=lowbit(x)) res += c[x];
	return res;
}

修改

结合上边的图我们发现如果修改了c(4),那么c(8)也会改变,所以修改的时候需要+=lowbit(x),把其虚拟的树中的父节点也改变。

void modify(int x, int d)
{
	for(;x<=n;x+=lowbit(x)) c[x] += d;
}

算法应用

单点修改区间查询

最简单的树状数组模板就可以完成

#include <bits/stdc++.h>
#define ll long long
#define int long long
using namespace std;
const int N = 2e5+10;
int n, q;
ll a[N];
ll c[N];

int query(int x)
{
	ll res = 0;
	for(; x; x -= x & (-x)) res+=c[x];
	return res;
}


void modify(int x, ll s)
{
	for(;x <= n;x += x & (-x)) c[x] += s;
}

signed main()
{
	scanf("%lld%lld",&n,&q);
	for(int i = 1; i <= n; i ++)
	{
		scanf("%lld",&a[i]);
		modify(i, a[i]);
	}
	int op, x;
    ll d;
	while(q--)
	{
		scanf("%lld%lld",&op, &x);
		if(op==1)
		{
			scanf("%lld", &d);
		*斜体样式*	modify(x, d - a[x]);
            a[x] = d;
		}
		else
		{
			printf("%lld\n", query(x));
		}
	}
	
}

区间修改区间查询

树状数组做到区间修改需要维护差分数组才能做到。
差分数组的前缀和是原数组。
那么我们可以推导公式:
a1 = d1
a2 = d1 + d2
a3 = d1 + d2 + d3

sum1 = a1 = d1
sum2 = a1 + a2 = 2d1 + d2
sum3 = a1 + a2 + a3 = 3d1 +2d2 + d3

所以sum(x) = nd1 + (x-1)d2 + (x-2)d3 + … + dn

可以推导出:

在这里插入图片描述

如题:

1、求区间和 2、使区间所有数+d

那么这题根据上述公式开两个树状数组就可以解决

			// 添加
  			modify1(l, d);
            modify1(r+1,-d);
            modify2(l, l*d);
            modify2(r+1,(r+1)*(-d));
			
			// 查询
			printf("%lld\n", (x+1)*query1(x)-query2(x));

求逆序对

求逆序对是一个静态的问题,我们需要将静态转化为动态。即一边插入一边统计逆序对的个数。每一次把一个新的a[i]放入的时候,通过树状数组查询出已经存在的数中比它大的数的个数,并且可以肯定这些比它大的数一定是在a[i]前面的,一定构成逆序对。所以树状数组是用来统计每个数出现的次数。

移步我的另一篇超详细的文章:
https://blog.csdn.net/qq_41829492/article/details/124073384

树状数组上二分

https://blog.csdn.net/qq_41829492/article/details/123984745

有错误请提出 我会立即纠正

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

善良的大铁牛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值