树状数组

mac2025-10-03  2

集训时摸的鱼都是今天的泪。 问题背景:一个序列,求相同两个字母之间不同字母的个数。 问题延伸:求区间内不同数字的个数。 结合《挑战》与树状数组彻底入门,算法小白都看得懂的超详细解析 树状数组的原理与实现

BIT的原理

把线段树不需要的右儿子都扔掉了 为什么要利用lowbit函数?对树状数组有什么作用么?那么我们仍然利用上面的表格,得到这样的图:

如果把lowbit函数的值作为这个位置储存的区域和,这样就可以完美覆盖所有位置。也就是,每一个数字管理一段区间。就像线段树一样。这就是为什么我们要利用lowbit函数。

我们在维护、使用树状数组的时候,利用累加。原来数组上利用累加的时候是i++,这样遍历一次数组时间复杂度为O(n),我们既然可以利用lowbit,那么累加的时候将i++改为i += lowbit(i),这样虽然我们还是在原数组上跳跃,但是可以抽象成在一个树上按顺序遍历。

https://blog.csdn.net/iwts_24/article/details/82497026

BIT的结构

BIT使用数组维护下图所示的部分和 直观地看,最后有k个连续的0,区间长度就为k,维护的值就是包括自己往前数2^k个。 1000 c[8]=a[8]+…+a[1] 0111 c[7]=a[7] 0110 c[6]=a[6]+a[5] 0101 c[5]=a[5]

BIT的求和

区间查询

利用C[i]数组,来求A数组前i项的和 sum[7]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7] ; 前i项和 C[4]=A[1]+A[2]+A[3]+A[4]; C[6]=A[5]+A[6]; C[7]=A[7]; 可以推出: sum[7]=C[4]+C[6]+C[7]; 序号写为二进制: sum[(111)]=C[(100)]+C[(110)]+C[(111)];

111 110 (111-1,111-2^0) 100 (110-10, 110-2^1) 0 (100-100,100-2^2)

sum[5]=A[1]+A[2]+A[3]+A[4]+A[5] ; 前i项和

C[4]=A[1]+A[2]+A[3]+A[4]; C[5]=A[5];

可以推出: sum[5]=C[4]+C[5];

序号写为二进制: sum[(101)]=C[(100)]+C[(101)];

101 100 (101-1,101-2^0) 0 (100-100,100-2^2)

int lowbit(int x) { return x&(-x); } int getsum(int x) //x是下标 { int sum=0; while(x) { sum+=low[x]; x-=lowbit(x); } return sum; }

BIT值的更新

1000 c[8]=a[8]+…+a[1] 0111 c[7]=a[7] 0110 c[6]=a[6]+a[5] 0101 c[5]=a[5] 我们看到,c[5]的值被c[6],c[8]用到了,因此在更新c[5]的时候要同步更新c[6]和c[8],这是个反向的操作 c[7]只需要set自己 也就是说从小到大通过加lowbit的方式,只更新包含自己的区间~

//n是序列总数 void update(int x,int val) { while(x<=n) { low[x]+=val; x+=lowbit(x); } } 101 + 1 = 110 110 + 10 = 1000 1000 + 1000 = 10000

加上或减去lowbit(),都是使末尾的0更多的操作

求区间内不同数字的个数

思路:维护前i个数中不同数字的个数,那么做个区间的差即可。如何维护不同的数字个数呢,那就是只记录在最后一次出现的位置。第一次出现的时候,在出现的位置update(位置,1),把该位置记做pre。数字再次出现的时候,update(pre,-1),当前位置再加1。那么同一个文件就只算了一次。注意当我在update(pre,-1)时,破坏了之前的结果,因此我们要按照查询区间的右端点来排序,当扫到右端点的时候,就计算一次答案。

手痒痒了orz

贴个别人的代码留个印象 添加链接描述

#include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int maxn=100010; int num[maxn]; ///存储数列 int tree[maxn]; ///树状数组 int book[maxn]; int out[maxn]; ///存储结果 int N; struct node { int l,r; int pos; }ask[maxn]; ///存储询问数组 bool cmp(node x,node y) ///按 r 从小到大排序 { return x.r<y.r; } int lowbit(int n) ///树状数组 { return n&(-n); } void add(int n,int v) ///更新值 { while(n<=N) { tree[n]+=v; n+=lowbit(n); } return; } int sum(int n) ///树状数组求前n个结果 { int result=0; while(n) { result+=tree[n]; n-=lowbit(n); } return result; } int main() { int Q; while(~scanf("%d%d",&N,&Q)) { memset(book,0,sizeof(book)); memset(tree,0,sizeof(book)); memset(out,0,sizeof(out)); for(int i=1;i<=N;i++) scanf("%d",&num[i]); for(int i=1;i<=Q;i++){ scanf("%d%d",&ask[i].l,&ask[i].r); ask[i].pos=i; ///因为下面要排序,为了记忆,故要标记下是第几个 } sort(ask+1,ask+1+Q,cmp); int temp=1;///看似二重循环,其实也只是从小到大扫了一遍 ///因为值r已经排好序了,故j是从1开始,一直要最大的r。 for(int i=1;i<=Q;i++) { for(int j=temp;j<=ask[i].r;j++) { if(book[num[j]]) ///之前有出现过,在之前的位置减1 { add(book[num[j]],-1); } add(j,1); /// 在这个位置加1 ,为什么加的是1呢,因为我们算的是不同数字的个数, ///不是值的和,跟刚开始入门树状数组中求的和的有些不同的。 book[num[j]]=j;///用book数组存储值num[j] 的位置 } temp=ask[i].r+1; ///表示下次开始的位置 out[ask[i].pos]=sum(ask[i].r)-sum(ask[i].l-1);///用out数组存储每组询问的结果 } for(int i=1;i<=Q;i++) ///输出结果 printf("%d\n",out[i]); } return 0; }
最新回复(0)