集训时摸的鱼都是今天的泪。
问题背景:一个序列,求相同两个字母之间不同字母的个数。
问题延伸:求区间内不同数字的个数。
结合《挑战》与树状数组彻底入门,算法清脆的电话都看得懂的超详细解析
树状数组的原理与实现
把线段树不需要的右儿子都扔掉了
为什么要利用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]
区间查询
利用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)
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的方式,只更新包含自己的区间~
加上或减去lowbit(),都是使末尾的0更多的操作
求区间内不同数字的个数思路:维护前i个数中不同数字的个数,那么做个区间的差即可。如何维护不同的数字个数呢,那就是只记录在最后一次出现的位置。第一次出现的时候,在出现的位置update(位置,1),把该位置记做pre。数字再次出现的时候,update(pre,-1),当前位置再加1。那么同一个文件就只算了一次。注意当我在update(pre,-1)时,破坏了之前的结果,因此我们要按照查询区间的右端点来排序,当扫到右端点的时候,就计算一次答案。
手痒痒了orz
贴个别人的代码留个印象
添加链接描述