树状数组的基本操作

不得不说,树状数组很优雅,网上前几篇博文写的很容易理解,在这里主要是记录下自己的学习。

先来张图,方便理解


树状数组最主要的几个部分,

一是求lowbit,

一个正数的相反数的二进制是正数的二进制取反加1,因此它们之间进行&运算,便得到了lowbit——一个数二进制的只保留一个最低位的1,高位的1全部清空。

int lowbit(int k){
	return k&-k;
}
通过原数组a构建树状数组c,之后便维护c数组就ok。

所以,现在需要看a数组和c数组的联系,c数组的某个元素其实是若干个a数组中的元素的和,c[i]有lowbit(i)个a数组元素构成,即从a[i]开始然后往前找lowbit(i)个,然后相加得和即为c[i]的值。

所以构建c数组的代码如下

void build(int jk[], int n){
	int i, k, res, j;
	for(i = 1; i <= n; ++i){
		k = lowbit(i);
		res = 0, j = i;;
		while(k){
			res += jk[j];
			--j, --k;
		}
		c[i] = res;
	}
}

仔细看看图,其实能够知道,c[i]都是只会影响到c[i]+lowbit(i),因此更新完单点的数值之后,

向上上溯更新能够影响到的点。

void update(int value, int k, int n){
	while(k <= n){
		c[k] += value;
		k += lowbit(k);
	}
}

求和每次会得到始点到某个点内的和,所以将更新的过程逆过来,便可以求和

int getSum(int k){
	int ans = 0;
	while(k){
		ans += c[k];
		k -= lowbit(k);
	}
	return ans;
}

理解了之后树状数组很好用的

#include <iostream>
#include <cstdio>
using namespace std;
int c[55];
int lowbit(int k){
	return k&(-k);
}
void build(int jk[], int n){
	int i, k, res, j;
	for(i = 1; i <= n; ++i){
		k = lowbit(i);
		res = 0, j = i;;
		while(k){
			res += jk[j];
			--j, --k;
		}
		c[i] = res;
	}
}
void update(int value, int k, int n){
	while(k <= n){
		c[k] += value;
		k += lowbit(k);
	}
}
int getSum(int k){
	int ans = 0;
	while(k){
		ans += c[k];
		k -= lowbit(k);
	}
	return ans;
}
int main(){
	int i, jk[55] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
	build(jk, 8);
	for(i = 1; i <= 8; ++i)
		printf("%d\n", getSum(i));
	return 0;
}

下面贴一个树状数组应用的代码,求给定数字序列的逆序数个数


#include <algorithm>
#include <iostream>
#include <cstdio>
using namespace std;
const int maxn = 1005;
int a[maxn], c[maxn] = {0}, n;
struct node{
	int value, index, ans;
} jk[maxn];
int cp1(node a, node b){
	return a.value > b.value;
}
int cp2(node a, node b){
	return a.index < b.index;
}
int lowbit(int k){
	return k&(-k);
}
void update(int k, int va){
	while(k <= n){
		c[k] += va;
		k += lowbit(k);
	}
}
int getSum(int k){
	int ans = 0;
	while(k){
		ans += c[k];
		k -= lowbit(k);
	}
	return ans;
}
int main(){
	int i, k, ans;
	cin >> n;
	for(i = 1; i <= n; ++i) {
		cin >> jk[i].value;
		jk[i].index = i;
	}
	sort(jk+1, jk+n+1, cp1);
	ans = 0;
	for(i = 1; i <= n; ++i){
		k = getSum(jk[i].index);
		update(jk[i].index, 1);		//将数从大到小插入c数组中,当然插入的位置是数值原本所在的位置					
		jk[i].ans = k;				//每次插入前求得有多少比当前数值大的数已经插入到当前数组位置的前面去了,便是逆序数
		ans += k;
	}
	sort(jk+1, jk+n+1, cp2);
	for(i = 1; i <= n; ++i){
		printf("%d %d\n", jk[i].value, jk[i].ans);
	}
	printf("%d\n", ans);
	return 0;
}

继续加油~

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
树状数组(Fenwick Tree)是一种用于快速维护数组前缀和的数据结构。它可以在 $O(\log n)$ 的时间内完成单点修改和前缀查询操作,比线段树更加简洁高效。 下面是 Java 实现的树状数组详解: 首先,在 Java 中我们需要使用数组来表示树状数组,如下: ``` int[] tree; ``` 接着,我们需要实现两个基本操作:单点修改和前缀查询。 单点修改的实现如下: ``` void update(int index, int value) { while (index < tree.length) { tree[index] += value; index += index & -index; } } ``` 该函数的参数 `index` 表示要修改的位置,`value` 表示修改的值。在函数内部,我们使用了一个 `while` 循环不断向上更新树状数组中相应的节点,直到到达根节点为止。具体来说,我们首先将 `tree[index]` 加上 `value`,然后将 `index` 加上其最后一位为 1 的二进制数,这样就可以更新其父节点了。例如,当 `index` 为 6 时,其二进制表示为 110,最后一位为 2^1,加上后变为 111,即 7,这样就可以更新节点 7 了。 前缀查询的实现如下: ``` int query(int index) { int sum = 0; while (index > 0) { sum += tree[index]; index -= index & -index; } return sum; } ``` 该函数的参数 `index` 表示要查询的前缀的结束位置,即查询 $[1, index]$ 的和。在函数内部,我们同样使用了一个 `while` 循环不断向前查询树状数组中相应的节点,直到到达 0 为止。具体来说,我们首先将 `sum` 加上 `tree[index]`,然后将 `index` 减去其最后一位为 1 的二进制数,这样就可以查询其前一个节点了。例如,当 `index` 为 6 时,其二进制表示为 110,最后一位为 2^1,减去后变为 100,即 4,这样就可以查询节点 4 的值了。 最后,我们还需要初始化树状数组,将其全部置为 0。初始化的实现如下: ``` void init(int[] nums) { tree = new int[nums.length + 1]; for (int i = 1; i <= nums.length; i++) { update(i, nums[i - 1]); } } ``` 该函数的参数 `nums` 表示初始数组的值。在函数内部,我们首先创建一个长度为 `nums.length + 1` 的数组 `tree`,然后逐个将 `nums` 中的元素插入到树状数组中。具体来说,我们调用 `update(i, nums[i - 1])` 来将 `nums[i - 1]` 插入到树状数组的第 `i` 个位置。 到此为止,我们就完成了树状数组的实现。可以看到,树状数组的代码比线段树要简洁很多,而且效率也更高。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值