树状数组

前言

本蒟蒻学习了7,8遍树状数组才学会,原因不是因为我笨,而是没有把根本的理念先搞懂,so,我打算写这篇博客,把树状数组根本剖析一下

用途

用于快速单点修改,快速查询前缀和,从而快速查询区间和

工作原理


其中有数字的点存储的是树状数组的 \(tr[]\) ,灰色节点则是为了方便理解而画出的子虚乌有的节点,一个节点上的tr值就是它下面所直连的子节点的和,为什么要这样设计呢,手模一下可以发现一个tr节点的高度就是它数字转化为二进制第一个1出现的位置,
再进一步手模发现,一个tr节点下面数字的和就是这个数字去掉最后一个1在加1到这个数字的中的所有 \(a_i\),抱歉这里表述的不太明白,还是看例子吧

修改,维护前缀和,只需要在最后的1加上1就可以了

lowbit(x)

$ lowbit(x)=x \& -x $ 功能是找到x的最后一个1

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int n,q,op,l,r;
int a[N],tr[N];
int lowbit(int x){
	return x&(-x);
}
void add(int x,int y){
	for(;x<=n;x+=lowbit(x)){
		tr[x]+=y;
	}
}
int query(int x){
	int res=0;
	for(;x;x-=lowbit(x)){
		res+=tr[x];
	}
	return res;
}
signed main(){
	scanf("%lld%lld",&n,&q);
	for(int i=1;i<=n;i++){
		scanf("%lld",&a[i]);
		add(i,a[i]);
	}
	for(int i=1;i<=q;i++){
		scanf("%lld%lld%lld",&op,&l,&r);
		if(op==1)  add(l,r);
		else  printf("%lld\n",query(r)-query(l-1));
	}
}

一本通oj例题

T2

我们注意到逆序对有两维的要求,我么们可以通过枚举一维顺序,再用树状数组维护所有在i前比它大的数的个数,就可以了

T3

对于这一类的题,先考虑dp,我们设 \(dp[i][j]\) 表示以第i个数为结尾,长度为j的严格上升子序列的个数

然后dp转移方程式就是 $$dp[i][j]=\sum_{a[k]<a[i],k<i}^k dp[k][j-1]$$

考虑因为有 \({a[k]<a[i],k<i}\) 的限制并且还有加和,所以用树状数组优化,对于每一个j开一维树状数组维护前缀和即可

注:答案要取模,我以后再也不犯这样傻逼的错误了

T4:

推式子

T5:

考虑一种思想,在一维的树状树组的每一个位置上分别再开一维树状数组,看代码可能就秒懂力(

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=(1<<12)+50;
int n,m,op,a,b,c,d;
int tr[N][N];
int lowbit(int x){
	return x&(-x);
}
void add(int x,int y,int z){
	for(int i=x;i<=n;i+=lowbit(i)){
		for(int j=y;j<=m;j+=lowbit(j)){
			tr[i][j]+=z;
		}
	}
}
int query(int x,int y){
	int res=0;
	for(int i=x;i;i-=lowbit(i)){
		for(int j=y;j;j-=lowbit(j)){
			res+=tr[i][j];
		}
	}
	return res;
}
signed main(){
	scanf("%lld%lld",&n,&m);
	while(scanf("%lld",&op)!=EOF){
		if(op==1){
			scanf("%lld%lld%lld",&a,&b,&c);
			add(a,b,c);
		}
		else{
			scanf("%lld%lld%lld%lld",&a,&b,&c,&d);
			printf("%lld\n",query(c,d)+query(a-1,b-1)-query(c,b-1)-query(a-1,d));
		}
	}
}

T6:

和区间修区间查一样的套路

维护4个树状数组即可

T7:

先考虑二维树状数组,发现好像空间会超不好办

注意到题目给的条件

不会有星星重叠。星星按 \(y\) 坐标增序给出,\(y\) 坐标相同的按 \(x\) 坐标增序给出。

我们考虑固定一维y升序考虑,出题人真良心,都不用自己排序了,然后只需要树状数组维护所有x小于当前点的x的个数就行了

T8:

傻逼ybt,它没写i<j的条件,害的我迷惑了半天

式子转化成 \(a[i]-i<a[j]-j\) 然后直接做就做完了,注意要离散化一下

T9:

非常巧妙的题,使我的脑袋旋转

首先我们每次只能选最大的值删除,所以删除顺序是固定的

考虑将一些元素从一个堆弹到另一个堆,会使这些元素顺序反过来

然后于是我们就将两个栈顶拼在一起,记录栈顶位置,然后每次在栈中寻找最大的元素,树状数组维护栈顶距离最大值中间有多少个元素即可

T10:

做过类似的题,还写过题解

解释起来有点复杂,我当时题解写了好久,算了,懒得写了

T11:

不会,咕了

posted @ 2024-11-04 11:37  daydreamer_zcxnb  阅读(16)  评论(0编辑  收藏  举报