不得不说,树状数组很优雅,网上前几篇博文写的很容易理解,在这里主要是记录下自己的学习。
先来张图,方便理解
树状数组最主要的几个部分,
一是求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;
}
继续加油~