树状数组模板总结

树状数组模板总结


开这篇文的目的是数据结构的代码太长,放在一篇里面就太冗杂了,所以就再开一篇记录各种模板。

树状数组

本质:动态维护前缀和。当然,也可以维护前缀最小,最大

前缀和是最常用的,最值只适用于特定情况

接口:

lowbit()//lowbit(k)就是把k的二进制的高位1全部清空,只留下最低位的1和后面的0,

比如10的二进制是1010,则lowbit(k) = lowbit(1010)

单点修改update()

前缀和查询query()

区间查询ask()

区间修改change()

结合差分思想

ll Lowbit(int x){return x & (-x);}

一维树状数组:(求和)

t[x]中储存的就是右端点为x,长为lowbit(x)的区间a[]的和。

1.单点修改,单点查询||区间查询。
void update(int x,int y){
	while(x <= n){
		t[x] += y;
		x += Lowbit(x);//上加下减
	}
}
ll query(int x){//查询1到x的前缀和
	ll ret = 0;
	while(x){
		ret += t[x];
		x -= Lowbit(x);
	}
	return ret;
}
int ask(int l,int r){
	return query(r)-query(l-1);//如果是单点查询,就是(l,l);
}
2.区间修改,单点查询。

通过差分,用树状数组来维护a的差分数组d[] ( d[i]=a[i]-a[i-1] ,a[0]=0)

修改:则 在修改a[l]->a[r]时,对d的影响仅在a[l-1],a[l]和a[r],a[r+1]两处

所以只需修改d[l]和d[r+1]两处,使l位置+x,r+1位置-x即可。

查询:通过d的前缀和查询即可。

int update(int x,int y){
	while(x<=n){t[x]+=y;x+=lowbit(x);}
}
int query(int x){
	int ret=0;
	while(x){ret+=t[x];x-=lowbit(x);}
	return ret;
}
int change(int l,int r,int z){
	update(l,z);update(r+1,-z);
}
int ask(int x){
	query(x);//因为是树状数组维护的是前缀和,所以就直接是a[x]=query(x);
}
3.区间修改,区间查询。

我们考虑对树状数组维护前缀和

∑ i = 1 p a [ i ] = ∑ i = 1 p ∑ j = 1 i d [ j ] \sum_{i=1}^{p}a[i]=\sum_{i=1}^{p}\sum_{j=1}^{i}d[j] i=1pa[i]=i=1pj=1id[j]

把d[j]提出来: = ∑ i = 1 p ( p − i + 1 ) ∗ d [ i ] =\sum_{i=1}^{p}(p-i+1)*d[i] =i=1p(pi+1)d[i]

用类似反演里的公式:重点

= ∑ i = 1 p ( p + 1 ) ∗ d [ i ] − ∑ i = 1 p i ∗ d [ i ] =\sum_{i=1}^{p}(p+1)*d[i]-\sum_{i=1}^{p}i*d[i] =i=1p(p+1)d[i]i=1pid[i]

所以这里我们维护两个基本数组:

s u m 1 [ i ] = d [ i ] sum1[i]=d[i] sum1[i]=d[i] s u m 2 [ i ] = i ∗ d [ i ] sum2[i]=i*d[i] sum2[i]=id[i].

void update(int x,int y){
	while(x<=n){
		sum1[x]+=y;
		sum2[x]+=x*y;
		x+=lowbit(x);
	}
}
int query(int x){
	int px=x,ret=0;
	while(x){
		ret+=sum1[x]*px-sum2[x];
		x-=lowbit(x);
	}
	return ret;
}
int ask(int l,int r){
	return query(r)-query(l-1);
}
int change(int l,int r,int z){
	update(l,z);update(r+1,-z);
}

二维树状数组(求和)

t[x][y]表示右下角端点为(x,y)的长为lowbit(x),宽为lowbit(y)的矩阵的和。

1.单点修改,单点查询||区间查询

其实就是一维变二维了。

void update(int x,int y,int z){
	int py=y;
	while(x<=n){
		y=py;
		while(y<=n){
			t[x][y]+=z;
			y+=lowbit(y);
		}
		x+=lowbit(x);
	}
}
int query(int x,int y){
	int py=y,ret=0;
	while(x){
		y=py;
		while(y){
			ret+=t[x][y];
			y-=lowbit(y);
		}
		x-=lowbit(x);
	}
	return ret;
}
int ask(int l1,int r1,int l2,int r2){
	return query(l2,r2)-query(l2,r1)-query(l1,r2)+query(l1,r1);
}
2.区间修改,单点查询

同一维树状数组,我们此时维护的是 d [ i ] [ j ] = a [ i ] [ j ] − a [ i − 1 ] [ j ] − a [ i ] [ j − 1 ] + a [ i − 1 ] [ j − 1 ] d[i][j]=a[i][j]-a[i-1][j]-a[i][j-1]+a[i-1][j-1] d[i][j]=a[i][j]a[i1][j]a[i][j1]+a[i1][j1]

对于每次修改,不同的是四个位置。

void update(int x,int y,int z){
	int py=y;
	while(x<=n){
		y=py;
		while(y<=n){t[x][y]+=z;y+=lowbit(y);}
		x+=lowbit(x);
	}
}
int query(int x,int y){//树状数组动态维护前缀和,此处直接就是a[x][y]的值。
	int py=y,ret=0;
	while(x){
		y=py;
		while(y){ret+=t[x][y];y-=lowbit(y);}
		x-=lowbit(x);
	}
	return ret;
}
void change(ll l1,ll l2,ll r1,ll r2,ll z){
	update(r1+1,r2+1,z);update(r1+1,l2,-z);update(l1,r2+1,-z);update(l1,l2,z);
}
3.区间修改,区间查询

**查询:**所求:

∑ i = 1 n ∑ j = 1 m a [ i ] [ j ] = ∑ i = 1 n ∑ j = 1 m ∑ x = 1 i ∑ y = 1 j d [ x ] [ y ] = ∑ i = 1 n ∑ j = 1 m ( n − i + 1 ) ∗ ( m − j + 1 ) ∗ d [ i ] [ j ] \sum_{i=1}^{n}\sum_{j=1}^{m}a[i][j]=\sum_{i=1}^{n}\sum_{j=1}^{m}\sum_{x=1}^{i}\sum_{y=1}^{j}d[x][y]=\sum_{i=1}^{n}\sum_{j=1}^{m}(n-i+1)*(m-j+1)*d[i][j] i=1nj=1ma[i][j]=i=1nj=1mx=1iy=1jd[x][y]=i=1nj=1m(ni+1)(mj+1)d[i][j]

然后展开

( n − i + 1 ) ∗ ( m − j + 1 ) ∗ d [ i ] [ j ] = ( n + 1 ) ∗ ( m + 1 ) ∗ d [ i ] [ j ] − ( n + 1 ) ∗ j ∗ d [ i ] [ j ] − ( m + 1 ) ∗ i ∗ d [ i ] [ j ] + d [ i ] [ j ] ∗ i ∗ j (n-i+1)*(m-j+1)*d[i][j]=(n+1)*(m+1)*d[i][j]-(n+1)*j*d[i][j]-(m+1)*i*d[i][j]+d[i][j]*i*j (ni+1)(mj+1)d[i][j]=(n+1)(m+1)d[i][j](n+1)jd[i][j](m+1)id[i][j]+d[i][j]ij

所以需要这四个:

( n + 1 ) ∗ ( m + 1 ) ∑ i = 1 n ∑ j = 1 m d p [ i ] [ j ] (n+1)*(m+1)\sum_{i=1}^{n}\sum_{j=1}^{m}dp[i][j] (n+1)(m+1)i=1nj=1mdp[i][j]

( n + 1 ) ∑ i = 1 n ∑ j = 1 m j ∗ d [ i ] [ j ] (n+1)\sum_{i=1}^{n}\sum_{j=1}^{m}j*d[i][j] (n+1)i=1nj=1mjd[i][j]

( m + 1 ) ∑ i = 1 n ∑ j = 1 m i ∗ d [ i ] [ j ] (m+1)\sum_{i=1}^{n}\sum_{j=1}^{m}i*d[i][j] (m+1)i=1nj=1mid[i][j]

∑ i = 1 n ∑ j = 1 m i ∗ j ∗ d [ i ] [ j ] \sum_{i=1}^{n}\sum_{j=1}^{m}i*j*d[i][j] i=1nj=1mijd[i][j]

所以维护四个基本数组
sum1[i][j]=d[i][j]

sum2[i][j]=i*d[i][j]

sum3[i][j]=j*d[i][j]

sum4[i][j]=ijd[i][j]

void update(ll x,ll y,ll z){
	ll px=x,py=y;
	while(x<=n){
		y=py;
		while(y<=m){
			sum1[x][y]+=z;
			sum2[x][y]+=z*px;
			sum3[x][y]+=z*py;
			sum4[x][y]+=z*px*py;
			y+=lowbit(y);
		}
		x+=lowbit(x);
	}
}
ll query(ll x,ll y){
	ll px=x,py=y,ret=0;
	while(x){
		y=py;
		while(y){
			ret+=(px+1)*(py+1)*sum1[x][y]-(py+1)*sum2[x][y]-(px+1)*sum3[x][y]+sum4[x][y];
			y-=lowbit(y);
		}
		x-=lowbit(x);
	}
	return ret;
}
void change(ll l1,ll l2,ll r1,ll r2,ll z){
	update(r1+1,r2+1,z);update(r1+1,l2,-z);update(l1,r2+1,-z);update(l1,l2,z);
}
ll ask(ll l1,ll l2,ll r1,ll r2){
	return query(r1,r2)-query(r1,l2-1)-query(l1-1,r2)+query(l1-1,l2-1);
}

树状数组求最值


树状数组仅能在单点修改,区间查询求最值:

设原数序列为a[],树状数组为t[].

修改是要修改每个包含x的区间。因为x向上增的lowbit()是固定的,所以每次就以当前点为终点,枚举每个起点,更改树状数组的值。

查询是很好理解的,就是向倍增一样的不断往前靠。

void update(int x){
	while(x<=n){
		t[x]=a[x];
		int lx=lowbit(x);
		for(int i=1;i<lx;i<<=1)
			t[x]=max(t[x],t[x-i]);
		x+=lx;
	}
}
int query(int x,int y){
	int ret=0;
	while(y>=x){
		ret=max(a[y],ret);
		y--;
		for(;y-lowbit(y)>=x;y-=lowbit(y))
			ret=max(ret,t[y]);
	}
	return ret;
}

时间复杂度均为 O ( l o g 2 n ) O(log^2{n}) O(log2n)


预备知识:

原码&&反码&&补码:

一、正整数的原码、反码、补码完全一样,即符号位固定为0,数值位相同

二、负整数的符号位固定为1,由原码变为补码时,规则如下:

​ 1、原码符号位1不变,整数的每一位二进制数位求反,得到反码

​ 2、反码符号位1不变,反码数值位最低位加1,得到补码

树状数组维护的是前缀和。

二维差分

修改矩阵中(x1,y1)->(x2,y2)中的所有值。

修改四个点:
a[x1][y1]+=p;a[x2+1][y2+1]+=p;

a[x1+1][y1]-=p;a[x1][y2+1]-=p;

然后跑二维前缀和即可。

a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1];


参考:https://www.cnblogs.com/RabbitHu/p/BIT.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值