【整活】如何不用树状数组刷完一本通树状数组

前言

刚开始刷树状数组的时候只会模板,后面的题压根不会,于是就用别的算法水了几道。

后来板刷一本通的时候,感觉要不整个活就太亏了,于是有了这篇文章

「一本通 4.1 例 1」数列操作

原题

算法:分块

树状数组板子题,用分块过了。

代码

#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
const int N=1e5+10;
int a[N],pos[N],L[N],R[N],add[N],sum[N];
int n,t,m;
inline void init(){
	t=sqrt(n);
	for(int i=1;i<=t;i++) L[i]=(i-1)*t+1,R[i]=i*t;
	if(R[t]<n) t++,L[t]=R[t-1]+1,R[t]=n;
	for(int i=1;i<=t;i++){
		sum[i]=0,add[i]=0;
		for(int j=L[i];j<=R[i];j++) pos[j]=i,sum[i]+=a[j];
	}
}
inline void change(int x,int k){
	a[x]+=k,sum[pos[x]]+=k;
}
inline int query(int l,int r){
	int p=pos[l],q=pos[r],ans=0;
	if(p==q){
		for(int i=l;i<=r;i++) ans+=a[i]+add[p];
	}
	else{
		for(int i=p+1;i<=q-1;i++) ans+=sum[i];
		for(int i=l;i<=R[p];i++) ans+=a[i]+add[pos[i]];
		for(int i=L[q];i<=r;i++) ans+=a[i]+add[pos[i]];
	}
	return ans;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) cin>>a[i];
	init();
	while(m--){
		int k,a,b;
		scanf("%d%d%d",&k,&a,&b);
		if(!k) printf("%d\n",query(a,b));
		else change(a,b);
	}
}

「一本通 4.1 例 2」数星星 Stars

原题

算法:动态开点权值线段树

正解应该是权值树状数组,但我用了权值线段树,但空间不够,于是动态开点。

代码

#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
const int N=6e4+10;
struct node{
	long long num;
	int ls,rs;
}tr[90005];
int tot=0;
inline void pushup(int x){
	tr[x].num=tr[tr[x].ls].num+tr[tr[x].rs].num;
}
void insert(int &x,int l,int r,int k){
	if(!x) x=++tot;
	if(l==r){
		tr[x].num++;
		return;
	}
	int mid=(l+r)/2;
	if(k<=mid) insert(tr[x].ls,l,mid,k);
	else insert(tr[x].rs,mid+1,r,k);
	pushup(x);
}
int query(int x,int l,int r,int ql,int qr){
	if(!x) return 0;
	if(l>=ql&&r<=qr) return tr[x].num;
	int mid=(l+r)/2,sum=0;
	if(ql<=mid) sum+=query(tr[x].ls,l,mid,ql,qr);
	if(qr>mid) sum+=query(tr[x].rs,mid+1,r,ql,qr);
	return sum;
}
int n,root;
int main(){
	scanf("%d",&n);
	while(n--){
		int x,y;
		scanf("%d%d",&x,&y);
		printf("%d\n",query(root,0,N,0,x));
		insert(root,0,N,x);
	}
}

「一本通 4.1 练习 1」清点人数

原题

算法:线段树

可以说是线段树板子题吧,自然是要用线段树的捏

代码

#include <bits/stdc++.h>
using namespace std;
const int N=5e5+10;
int tr[N*4];
inline void pushup(int x){
	tr[x]=tr[x*2]+tr[x*2+1];
}
void insert(int x,int l,int r,int k,int p){
	if(l==r){
		tr[x]+=p;
		return;
	}
	int mid=(l+r)/2;
	if(k<=mid) insert(x*2,l,mid,k,p);
	else insert(x*2+1,mid+1,r,k,p);
	pushup(x);
}
int query(int x,int l,int r,int ql,int qr){
	if(l>=ql&&r<=qr) return tr[x];
	int mid=(l+r)/2,sum=0;
	if(ql<=mid) sum=query(x*2,l,mid,ql,qr);
	if(qr>mid) sum+=query(x*2+1,mid+1,r,ql,qr);
	return sum;
}
int n,k;
int main(){
	scanf("%d%d",&n,&k);
	while(k--){
		char opt[2];
		int m,p;
		scanf("%s",opt);
		if(opt[0]=='A'){
			scanf("%d",&m);
			printf("%d\n",query(1,1,n,1,m));
		}
		else if(opt[0]=='B'){
			scanf("%d%d",&m,&p);
			insert(1,1,n,m,p);
		}
		else{
			scanf("%d%d",&m,&p);
			insert(1,1,n,m,-p);
		}
	}
}

「一本通 4.1 练习 2」简单题

原题

算法:线段树

区间取反,自然是要用可爱的线段树啦,维护个懒标记即可。

代码

#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n,m,tr[N*4],add[N*4];
inline void pushdown(int x){
	if(add[x]){
		add[x]=0;
		add[x*2]^=1,add[x*2+1]^=1;
	}
}
void update(int x,int l,int r,int ql,int qr){
	
	if(l>=ql&&r<=qr){
		add[x]^=1;
		return;
	}
	pushdown(x);
	int mid=(l+r)/2;
	if(ql<=mid) update(x*2,l,mid,ql,qr);
	if(qr>mid) update(x*2+1,mid+1,r,ql,qr);
}
int query(int x,int l,int r,int k){
	if(l==r) return add[x];
	pushdown(x);
	int mid=(l+r)/2;
	if(k<=mid) return query(x*2,l,mid,k);
	return query(x*2+1,mid+1,r,k);
}
int main(){
	scanf("%d%d",&n,&m);
	while(m--){
		int opt,l,r;
		scanf("%d%d",&opt,&l);
		if(opt==1){
			scanf("%d",&r);
			update(1,1,n,l,r);
		}
		else printf("%d\n",query(1,1,n,l));
	}
}

「一本通 4.1 练习 3」打鼹鼠

原题

算法:线段树+暴力+剪枝

本来想写二逼线段树的,但是xyz太懒了,不想写那么高难度的算法,于是她想暴力。

她写了个一维暴力,二维线段树的 O ( n 2 log ⁡ n ) O(n^2\log n) O(n2logn)暴力算法,果然挂了(73)

然后聪明的xyz就加了个剪枝(若一行里个数为0直接返回),就过了!!!

代码

#include <bits/stdc++.h>
using namespace std;
const int N=2100;
int tot=0,root[N],ls[N*N],rs[N*N],tr[N*N];
inline void pushup(int x){
	tr[x]=tr[ls[x]]+tr[rs[x]];
}
void build(int &x,int l,int r){
	x=++tot;
	if(l==r) return;
	int mid=(l+r)/2;
	build(ls[x],l,mid),build(rs[x],mid+1,r);
	pushup(x);
}
void add(int x,int l,int r,int k,int p){
	tr[x]+=p;
	if(l==r) return;
	int mid=(l+r)/2;
	if(k<=mid) add(ls[x],l,mid,k,p);
	else add(rs[x],mid+1,r,k,p);
}
int query(int x,int l,int r,int ql,int qr){
	if(!tr[x]) return 0;
	if(ql<=l&&r<=qr) return tr[x];
	int mid=(l+r)/2,sum=0;
	if(ql<=mid) sum=query(ls[x],l,mid,ql,qr);
	if(qr>mid) sum+=query(rs[x],mid+1,r,ql,qr);
	return sum;
}
int n,m;
int main(){
	scanf("%d",&n);
	for(int i=0;i<n;i++) build(root[i],0,n-1);
	while(cin>>m&&m!=3){
		if(m==1){
			int x,y,k;
			scanf("%d%d%d",&x,&y,&k);
			add(root[x],0,n-1,y,k);
		}
		else{
			int x,y,x2,y2,sum=0;
			scanf("%d%d%d%d",&x,&y,&x2,&y2);
			for(int i=x;i<=x2;i++) sum+=query(root[i],0,n-1,y,y2);
			printf("%d\n",sum);
		}
	}
}

后记

本文是xyz的整活,如果你真的想学树状数组,千万不要像她这么干

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
树状数组(Fenwick Tree)是一种用于快速维护数组前缀和的数据结构。它可以在 $O(\log n)$ 的时间内成单点修改和前缀查询操作,比线段树更加简洁高效。 下面是 Java 实现的树状数组详解: 首先,在 Java 中我们需要使用数组来表示树状数组,如下: ``` int[] tree; ``` 接着,我们需要实现两个基本操作:单点修改和前缀查询。 单点修改的实现如下: ``` void update(int index, int value) { while (index < tree.length) { tree[index] += value; index += index & -index; } } ``` 该函数的参数 `index` 表示要修改的位置,`value` 表示修改的值。在函数内部,我们使用了一个 `while` 循环不断向上更新树状数组中相应的节点,直到到达根节点为止。具体来说,我们首先将 `tree[index]` 加上 `value`,然后将 `index` 加上其最后一位为 1 的二进制数,这样就可以更新其父节点了。例如,当 `index` 为 6 时,其二进制表示为 110,最后一位为 2^1,加上后变为 111,即 7,这样就可以更新节点 7 了。 前缀查询的实现如下: ``` int query(int index) { int sum = 0; while (index > 0) { sum += tree[index]; index -= index & -index; } return sum; } ``` 该函数的参数 `index` 表示要查询的前缀的结束位置,即查询 $[1, index]$ 的和。在函数内部,我们同样使用了一个 `while` 循环不断向前查询树状数组中相应的节点,直到到达 0 为止。具体来说,我们首先将 `sum` 加上 `tree[index]`,然后将 `index` 减去其最后一位为 1 的二进制数,这样就可以查询其前一个节点了。例如,当 `index` 为 6 时,其二进制表示为 110,最后一位为 2^1,减去后变为 100,即 4,这样就可以查询节点 4 的值了。 最后,我们还需要初始化树状数组,将其全部置为 0。初始化的实现如下: ``` void init(int[] nums) { tree = new int[nums.length + 1]; for (int i = 1; i <= nums.length; i++) { update(i, nums[i - 1]); } } ``` 该函数的参数 `nums` 表示初始数组的值。在函数内部,我们首先创建一个长度为 `nums.length + 1` 的数组 `tree`,然后逐个将 `nums` 中的元素插入到树状数组中。具体来说,我们调用 `update(i, nums[i - 1])` 来将 `nums[i - 1]` 插入到树状数组的第 `i` 个位置。 到此为止,我们就成了树状数组的实现。可以看到,树状数组的代码比线段树要简洁很多,而且效率也更高。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值