树状数组整理(1.基本原理)

//树状数组简写BIT,原名Binary Indexed Tree,直译似乎是二叉索引树?……


先来提一下lowbit,我们定义lowbit(x)是x的二进制中最右端的1连同后面所有的0构成的这么一个二进制数
比如……我们算一下lowbit(2222)吧,2222对应二进制是100010101110,最右端的1带上后面0那就是10(二进制),换算成十进制就是2,那么lowbit(2222)=2;再比如992,二进制下是1111100000,那么lowbit(992)=100000(二进制)=32(十进制)
怎么用程序算呢?首先直接一位一位跑就能搞,但这样实在是太原始,不符合我们对速度的要求……

在计算机中,整数是补码来表示的,所以-x就是x按位取反再+1所得,即-x=(~x)+1。按位取反后,x末尾的一串0全变成了1,最右边的1变成了0,其它位当然也变了;此时给(~x)加上1,右边一串1又变回了0,最右边的0又变回了1,而前面所有位不会变回去。那么把x和-x做一下与运算,显然前边不同的位都消去了,最后保留下的正好是x最右端的1和后面的一串0……这样就得到了lowbit(x)=x&-x
一个数字x如果减去lowbit(x),相当于在二进制中去掉了最后一个1;如果加上lowbit(x)就会进位,末尾连续0的个数至少增加1


至于树状数组,是用来维护一个数组a[]前缀和的一种工具,支持单点修改(a[p]+=k)和求前缀和(sum(p)=a[0]+...+a[p])
它维护一个新数组c[],使得 c[i]=a[i-lowbit(i)+1]+a[i-lowbit(i)+2]+......+a[i]


也就是说c[i]管了包括它自己向前的长为lowbit(i)的一段在a[]数组中对应的和……那么显然,如果我们要求一个前缀和,肯定得做出一个无缝拼接>_<~比如说搞sum(p)吧,可以拆成 sum(p)=sum(p-lowbit(p))+c[p],然后递归……求先不吐槽,这个递归是不是一定能求出值(停下来)呢?这个其实没啥好说的,一个数不停滴减去lowbit这样迭代,末尾的0肯定会不断增加,最终变成0,前边已经确认了是无缝,所以求出来值了,好简单>_< ……
另外这个递归可以直接拆出来,更快些,这样求和函数有了:
_int sum(int p) {_int s=0; for (; p; p-=p&-p) s+=c[p]; return s;} //_int是自己def的一种整数,以后可能用int会吃不开


至于修改会不会也和lowbit有关呢?试一试一路+lowbit发现正好可以,好的,我们发现了一个规律,用它就可以实现修改了……
其实那啥,我个人没有找到关于为什么要一路+lowbit的具体论述/证明,找到的所有资料里都是各种找规律验证(无意冒犯,感觉其实有规律能用就够了),所以下面内容纯属YY,拿出来讨论而已,如果有错误烦请留言更正,先行谢过
如何修改呢,a[p]+=k之后,首先肯定要c[p]+=k,再考虑有哪些其它值受到了影响:首先比下标比p还小的c[p']不可能包含a[p],不管它;下标比p大的c[p']可能包含a[p],按下标p'与lowbit(p)的相对大小分类讨论下,希望包含a[p]的c[p']不要太多…………
假设给p加上一个dp之后能覆盖到p,那必有lowbit(p+dp)>dp,不取等,让我们开启二进制视角:
  dp<lowbit(p),因为dp不足位数,所以lowbit(p+dp)=lowbit(dp)<=dp,不可能满足条件
  dp=lowbit(p),考虑到给最后一个1加上1它会进位,所以有lowbit(p+dp)>lowbit(p)=dp,也就是说它正好符合条件c[p+lowbit(p)]+=k
  dp>lowbit(p),这个怎么想呢……首先想覆盖到p就得覆盖到这段的开头p'=p+lowbit(p)吧,能覆盖到这段的开头那就得是p"=p'+lowbit(p'),继续……
  但还是要说明条链上所有点都可以覆盖到p,首先lowbit(p+lowbit(p)+lowbit(p'))=lowbit(p'+lowbit(p'))>=2lowbit(p')>lowbit(p)+lowbit(p')能覆盖,其它同理T_T

这样,修改的函数也就有了:
void change(int p, _int k) {for (; p<=n; p+=p&-p) c[p]+=k;}


最后还是整合下吧,打个包裹其它地方用起来方便点

#define N 10000
#define inf 99999999
#define _int long long
struct BIT {
  _int n,c[N+1]; void init(int s) {n=s; memset(c,0,sizeof(c));}
  void change(int p, _int k) {for (; p<=n; p+=p&-p) c[p]+=k;}
  _int sum(int p) {_int s=0; for (; p; p-=p&-p) s+=c[p]; return s;}
};



完毕,比预想的短了点……

下期预告:稍做修改的BIT

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值