算法---树状数组

一、树状数组的概念

树状数组是利用了数的二进制特征进行检索的一种树状结构。关于树状数组的结构图可以看下这篇文章,这位博主的结构图很详细(树状数组结构图)那么树状数组我们是用来干什么的呢?其实它和前缀和与差分也是也是一样的,也是用来求解关于区间问题的,具体我们可以看看后面的代码展现来感受一下就知道了。

我们定义一个树状数组tree[],原始数组a[]

由图的结构我们可以知道tree[1]=a1

tree[2]=tree[1]+a2

tree[3]=a3

tree[4]=tree[2]+tree[3]+a4

.....

tree[8]=tree[4]+tree[6]+tree[7]+a8

我们会发现一个问题,该如何求出tree呢?这就需要在每次的二进制后面加上一,找到下一个数了。这就需要用到lowbit(x)=x&(-x)来找到二进制中的最后一个一了。

如:

x   x二进制  lowbit(x)  tree
1   1          1        tree[1]=a1
2   10         2        tree[2]=a1+a2  
3   11         1        tree[3]=a3
4   100        4        tree[4]=a4+a3+a2+a1
5   101        1        tree[5]=a5
6   110        2        tree[6]=a6+a5

相信规律很明显我就不细说了吧。 

二、树状数组的代码实现

(1)单点修改+区间查询 

#include<bits/stdc++.h>
using namespace std;
#define lowbit(x)((x)&-(x))
const int N=1e3+10;
int a[N];
int tree[N];
int n;

void up(int x,int d)
{
	while(x<=N){
		tree[x]+=d;
		x+=lowbit(x);
	}
}

int sum(int x)
{
	int ans=0;
	while(x>0){
		ans+=tree[x];
		x-=lowbit(x);
	}
	return ans;
}

int main()
{
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		up(i,a[i]);
	}
	cout<<sum(6)-sum(3)<<endl;//查看区间[4,6]的和
	//现在给a[5]+100
	up(5,100);
	cout<<sum(6)-sum(3)<<endl;
	return 0;
}

(2)区间修改+单点查询

区间修改用到了前面我们学到的拆分知识,修改区间[L,R]的值,用差分,差分数组的变化时d[l]++,

d[r+1]--;这里同样也是,具体的我们看下代码。

注意点是什么呢,与前面的(1)相比较up的第二个参数第一个是a[i],得出来的sum就是a[i]的前缀和,而在(2)点中,up接受的第二个参数是a[i]-a[i-1],那么sum[i]计算的就是a[i],相当于sum[i]是tree[i]的前缀和,也就是说,第二个参数接受的是什么,sum[i]计算得到的就是第二个参数相加累计得到的。

#include<bits/stdc++.h>
using namespace std;
#define lowbit(x)((x)&-(x))
const int N=1e3+10;
int n,m;
int a[N];
int tree[N];

void up(int x,int d)
{
	while(x<=N){
		tree[x]+=d;
		x+=lowbit(x);
	}	
}

int sum(int x)
{
	int ans=0;
	while(x>0){
		ans+=tree[x];
		x-=lowbit(x);
	}
	return ans;
}

int main()
{
	cin>>n>>m;
	int old=0;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		up(i,a[i]-old);
		old=a[i];
	}
	while(m--){
		int l,r,q;
		cin>>l>>r>>q;
		up(l,q);
		up(r+1,-q);
	}
	//输出a中的所有元素
	for(int i=1;i<=n;i++){
		cout<<sum(i)<<" ";
	}
	return 0;
}
/*
5 3
1 2 3 4 5
1 2 3
1 1 5
3 5 1

*/

(3)区间修改+区间查询

区间查询与区间查询我们就需要两个树状数组了。

原因是什么呢,看下推理公式就知道了。

  a1+a2+a3+....+ak
=d1+(d1+d2)+(d1+d2+d3)+...+(d1+d2+d3+...+dk)
=k*d1+(k-1)*d2+(k-2)*d3+...+dk
=k(d1+d2+...+dk)-(d2+2*d3+...+(k-1)*dk)
=d*(d1+...+dk)-(i-1)*di(i从1到k)

那么我们创建的两个树状数组一个实现的是d[i],一个实现的是(i-1)*di

代码展现:

#include<bits/stdc++.h>
using namespace std;
#define lowbit(x)((x)&-(x))
const int N=1e3+10;
int a[N];
int tree1[N],tree2[N];
int n,m;

void up1(int x,int d)
{
	while(x<=N){
		tree1[x]+=d;
		x+=lowbit(x);
	}
}

void up2(int x,int d)
{
	while(x<=N){
		tree2[x]+=d;
		x+=lowbit(x);
	}
}

int sum1(int x)
{
	int ans=0;
	while(x>0){
		ans+=tree1[x];
		x-=lowbit(x);
	}
	return ans;
}

int sum2(int x)
{
	int ans=0;
	while(x>0){
		ans+=tree2[x];
		x-=lowbit(x);
	}
	return ans;
}

int main()
{
	cin>>n>>m;
	int old=0;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		up1(i,a[i]-old);
		up2(i,(i-1)*(a[i]-old));
		old=a[i];
	}
	while(m--){
		int l,r,q;
		cin>>l>>r>>q;
		up1(l,q);
		up2(l,(l-1)*q);
		up1(r+1,-q);
		up2(r+1,-q*r);
	}
	//查看[3,5]
	cout<<5*sum1(5)-sum2(5)-((3-1)*sum1(3-1)-sum2(3-1));
	return 0;
}

/*
7 3
1 2 3 4 5 6 7 
1 4 2
3 6 6
1 3 1

*/

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菜到极致就是渣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值