树状数组详解

1.前言

正式开始讲解树状数组之前,我们先来思考一个问题,假如给你一个数组a[N],要你完成w次修改和q次查询区间和,这时你该怎么办?

如果说直接使用前缀和的话,那么查询操作时间复杂度为O(1),但是修改操作将变为O(n);但是如果不使用前缀和的话,那么查询操作将变为O(n),修改操作将变为O(1)。

那有没有一种方法能够将这两种操作兼顾呢,是有的。下面要讲解的树状数组就将这两种操作兼顾了,时间复杂度均为log(n),近乎完美的解决了上述问题。

 

2.lowbit函数

lowbit函数是树状数组中一个重要的辅助函数,用于计算一个数的最低位的1对应的值。具体来说,lowbit函数返回的是一个二进制数中最低位的1与其后面所有0构成的部分。

lowbit函数可以通过以下步骤来实现:
1. 对于给定的整数n,首先取其二进制的补码(补码是为了处理负数的情况,负数的补码表示是将其正数的二进制表示按位取反再加1)。
2. 对n取补码后,将其与n进行“与”运算,得到的结果就是最低位的1的值。

示例如下:
假设原始数n = 12,其二进制表示为1100,补码为1100。
则~n = 1100,补码为0100,与n = 1100进行“与”运算,得到的结果是0100。即最低位的1对应的值为4。

代码如下:

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

 

3.基本思想

树状数组的基本思想是基于二进制索引规则和位运算,通过一种精妙的方式来管理数组中的元素,从本质上说是将数组元素划分为不同的管理小组,每个管理小组管理一个或多个连续的元素。这种划分是基于二进制索引的,通过下标对数组中连续元素进行分组管理。每个管理小组中的元素都存储了原始数组中一部分元素的前缀和。

我们先来看下面这个著名的图:

通过看上面这个图,我们可以发现,数组中的元素被分成了一个个区间,具体如下:

我们给定输入的数组a[N],和另外一个数组c[N]。

c[1] = a[1],

c[2] = a[1] + a[2]

c[3] = a[3]

c[4] = a[1] + a[2] + a[3] + a[4]

c[5] = a[5]

c[6]=a[5]+a[6]

c[7]=a[7];

c[8]=a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]+a[8];

也就是说我们想找某一个数的前缀和,我们就需要这些区间的加和。

比如说 6 = 110 =100 + 10

那么s[6] = c[110] + c[110-lowbit(110)] = c[110] + c[100]

那s[7] = c[111] + c[110] + c[100]

顺势给出求和代码:

int query(int x)
{
	int res=0;
	for(int i=x;i>0;i-=lowbit(i))
	res+=c[i];
	
	return res;
}

看到这里,其实不免感叹设计者的精妙之处。

那么对于更新操作,其实根据上图我们也可以发现,如果我们要更新c[3],那么我们要接着更新 c[4],和c[8]。

而具体 3 = 11,那么每次更新操作其实都是加上lowbit(x)

即11  + lowbit(11)= 100 = c[4]

100 + lowbit(100)=1000 = c[8]

代码如下:

void add(int x,int k)
{
	for(int i=x;i<=n;i+=lowbit(i))
	c[i]+=k;
}

 

4.总结

下面给出模版题链接和代码,欢迎大家自行练习和交流提问

https://www.luogu.com.cn/problem/P3374

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

const int N=5e5+10;

int n,m;

int a[N];

int c[N];

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

void add(int x,int k)
{
	for(int i=x;i<=n;i+=lowbit(i))
	c[i]+=k;
}

int query(int x)
{
	int res=0;
	for(int i=x;i>0;i-=lowbit(i))
	res+=c[i];
	
	return res;
}

int main()
{
	cin>>n>>m;
	
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		add(i,a[i]);
	}
	
	for(int i=0;i<m;i++)
	{
		int flag;
		cin>>flag;
		
		if(flag==1)
		{
			int x,k;
			cin>>x>>k;
			add(x,k);
		}
		else
		{
			int x,y;
			cin>>x>>y;
			cout<<query(y)-query(x-1)<<endl;
		}
	}
	
    return 0;
} 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值