目录
树状数组
重点:维护前缀和;快速单点修改;快速区间求和;不能计算区间最值。
易错点:根节点一定要从1开始。因为lowbit(0)=0,会陷入死循环。
正整数x被 “ 二进制分解 ”,使区间[1,x]分成 logx 个子区间。(唯一分解性质)
小区间特点:若结尾为R,则【区间长度= R的二进制分解下“最小的2的次幂”】。
即 lowbit(R)。 // 取出二进制下最低位的1。
基本用途是维护序列的前缀和。即用c数组储存序列a每个小区间的和。
除树根外,每个内部节点 c [ x ] 的父节点是 c [ x + lowbit(x) ] 。
lowbit
它通过公式来得出k,其中k就是该值从末尾开始0的个数。
然后将其得出的结果加上x自身就可以得出当前节点的父亲节点的位置,
或者是x减去其结果就可以得出上一个父亲节点的位置。
比如当前是6,二进制就是0110,k为2,那么6+2=8,而C(8)则是C(6)的父亲节点的位置;
int LOWBIT(x){ return x & (-x); }
单点修改
当我们要对最底层的值进行更新时,那么它相应的父亲节点存储的和也需要进行更新。
查询前缀和
int QUERY(x){
result = 0;
while(right > 0){
result += fenwick[x]; x -= LOWBIT(x);
return result;
}
15=(1111)2,通过lowbit分解,可变成4数和:(1111)2=(1)2+(10)2+(100)2+(1000)2,
然后我们分析这个倒着跳的过程。减去15的最小的2的幂次2^0得到14。
减去14的最小的2的幂次2^1得到12。减去12的最小的2的幂次2^2得到8。
所以C(15) = C(14) + C(12) + C(8) + C(0),由图也可以得知,其结果是正确的。
除此之外,树状数组能够快速的求任意区间的和,
设sum(k) = A[1] + A[2] + ... + A[k],则A[i] + A[i+1] + ... + A[j] = sum(j) - sum(i-1)。
练习
- 输入n个位置,然后先按照y的顺序,如果相等则按照x的顺序,
- 最终求该坐标左下的星星的数目。
【分析】根据题目要求,y已经满足要求,那么只需要考虑x即可,
#include<stdio.h>
#include<string.h>
int c[32000+10];
int a[15000+10];
int lowbit(int x) { return x&(-x); }
void updata(int x,int d) {
while(x<=32001) {
c[x]=c[x]+d;
x=x+lowbit(x); }
}
int getsum(int x) {
int res = 0;
while(x>0) { res=res+c[x]; x=x-lowbit(x); }
return res;
}
int main() {
int n; int i,x,y;
while(scanf("%d",&n)!=EOF) {
memset(c,0,sizeof(c));
memset(a,0,sizeof(a));
for(i=0; i<n; i++) {
//因为y是升序,所以横坐标小于x的所有点都符合,关键。
scanf("%d%d",&x,&y);
//下标可能从0开始,所以要x+1
a[getsum(x+1)]++;
//求出横坐标小于x的所有stars个数,并记录到a中
updata(x+1,1); //更新区间
for(i=0; i<n; i++) {
printf("%d\n",a[i]);
}
}
return 0;
}
优缺点
拓展:多维树状数组
https://blog.csdn.net/cggwz/article/details/78420102
拓展:树状数组与逆序对
将每个数该在的位置离散化出来,然后每次把每个数该在的位置上加上1。
比如一个数该在的位置为x,那么用add(x)把这个位置加上1,
然后再用区间查询read(x)查询1~x的和,也就是可以知道前面有多少个数是比他小的了(包括那个数自己)。
再用已经插入的数的个数减去read(x),就算出了前面有多少个数比他大了。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
int n,tree[100010];
void add(int k,int num){
while(k<=n){
tree[k]+=num;
k+=k&-k;
}
}
int read(int k){
int sum=0;
while(k){
sum+=tree[k];
k-=k&-k;
}
return sum;
}
struct node{
int val,pos;
}a[100010];
bool cmp(node a,node b){ return a.val < b.val; }
int main(void){
int i,j,b[100010];
while(scanf("%d",&n)==1){
memset(tree,0,sizeof(tree));
for(i=1;i<=n;i++){
scanf("%d",&a[i].val);
a[i].pos = i;
}
sort(a+1,a+1+n,cmp);
int cnt = 1;
for(i=1;i<=n;i++){
if(i != 1 && a[i].val != a[i-1].val)
cnt++;
b[a[i].pos] = cnt;
}
LL sum = 0;
for(i=1;i<=n;i++){
add(b[i],1);
sum += (i - read(b[i]));
}
printf("%lld\n",sum);
}
return 0;
}
——时间划过风的轨迹,那个少年,还在等你。