树状数组

前置知识:lowbit运算

\(lowbit(x)\) 表示正整数 \(x\) 在二进制表示下最低位的 \(1\) 跟后面的 \(0\) 构成的数值 ,有 \(lowbit(x)=x\) & $ ($ ~\(~x+1)\) ,即 \(lowbit(x)=x\) & \(-x\),理由如下:

\(lowbit(x)\) 是最后一位 \(1\) 所以跟前面的位没啥关系,祂在二进制表示下肯定就是 \(1\) 跟上很多 \(0\), 我们将 \(lowbit(x)\) 所在位跟后面拿出来看,毋庸置疑整出来就是 \(lowbit(x)\)

x    : 1 0 0 ... 0 0
~x   : 0 1 1 ... 1 1
~x+1  : 1 0 0 ... 0 0

再说前面的位,每一位肯定是 \(0\) & \(1=0\) 或者 \(1\) & \(0=0\),对答案没有影响,不用理祂


模板

维护一个数组 \(a\) 的两种操作

  • \(a_x\gets a_x+k\)

  • 查询 \(\sum\limits_{i=l}^ra_i\)

对于第二个操作,转换一波 \(\sum\limits_{i=l}^ra_i=\sum\limits_{i=1}^ra_i-\sum\limits_{i=1}^{l-1}a_i\) ,变成维护前缀和


BIT

搞一波 $t_x=\sum\limits_{i=x-lowbit(x)+1}^xa_i $,说白了就是用 \(t_x\) 表示以 \(x\) 结尾的长度为 \(lowbit(x)\) 的区间和啊,配上图就变得神奇了,因为祂是一棵树:

magic

对于这棵树,有以下性质

  • \(t_x\) 表示区间长度为 \(lowbit(x)\)

  • \(x\) 的父节点是 \(x+lowbit(x)\)\(x\) 的子节点是 \(x-lowbit(x)\)

  • 树的深度是 \(\log_2n\)


前缀和

考虑查询前缀和 \(\sum\limits_{i=1}^xa_i\) ,根据定义,可以把祂分段搞了,即

\[\sum\limits_{i=1}^xa_i=t_x+\sum\limits_{i=1}^{x-lowbit(x)}a_i \]

套娃之后可以递归或者循环求掉

int ask(int x) {
	int rs=0;
	for( ; x; x-=x&-x) rs+=tr[x];
	return rs;
} 

单点加

考虑单点加操作,观察图,显然与 \(a_x\) 有关的有且仅有 \(x\) 所在那条链,根据上述性质,珂以得出找父节点的办法,就这样一直加上去就珂啦

void add(int x,int w)
	{ for( ; x<=MAXN; x+=x&-x) tr[x]+=w; } 

其中 \(MAXN\) 是上界


理解

本质上是二分(?),此外类似的可以维护最大值


EX

可以维护区间加 & 区间和,做法是差分。

设差分数组 \(d[i]=a[i]-a[i-1]\),那么 \(a[x]=\sum_{i=1}^xd_i\)

前缀和是什么捏,\(\sum_{i=1}^x a_i=\sum_{i=1}^x\sum_{j=1}^id_j=\sum_{j=1}^x(n-j+1)*d_k\)

然后用两个 bit 维护。

int tr[2][N];
void add(int op,int x,int v) {
	for( ; x<=n; x+=x&-x) tr[op][x]+=v;
}
int ask(int op,int x) {
	int res=0;
	for( ; x; x-=x&-x) res+=tr[op][x];
	return res; 
}
void modify(int l,int r,int val) {
	add(0,l,val), add(0,r+1,-val);
	add(1,l,val*l), add(1,r+1,-val*(r+1));
}
int query(int l,int r) {
	int ll=l*ask(0,l-1)-ask(1,l-1);
	int rr=(r+1)*ask(0,r)-ask(1,r);
	return rr-ll;
}
posted @ 2023-08-12 22:46  Hypoxia571  阅读(4)  评论(0编辑  收藏  举报