树状数组(简单介绍+例题分析)

树状数组

简单介绍:

树状数组就是一个类似于树形结构的数组,树状数组基本用途是用来维护前缀和。树状数组通常·用来解决一些单点修改,单点查询,区间修改,区间查询的问题。

树状数组和线段树的区别:

树状数组能解决的线段树都能解决,线段树能解决的,树状数组未必可以

那为什么还要学习树状数组呢?

因为它易于实现啊。线段树代码量比较大,树状树组则可以比较快的解决出问题,树状数组的作用就是为了简化线段树

lowbit运算

lowbit(x)的意思是计算二进制下“最低位的1和后面所有的0”构成的数值。

那我们怎么实现呢?

x=14,二进制下(1110),我们对其取反则会得到(0001),再加1得到(0010),我们取反后会使各个数位都不相同,此时再加1会进位到取反后倒着数第一个为0的值,这样回到取反前则是倒着数的第一个1,此时只有这两位同时为1,进行按位与(&)运算则可得到结果

所以

int lowbit(int x)
return x&(~x+1);

由于**对x取反加一的操作实际就是-**x,因此变换为:

int lowbit(int x)
return x&(-x);

差分思想:

原数组的差为差分数组,差分数组的前缀和即为原数组

给出数组a,对应差分数组为b

a 1 3 4 5 7 8 4
b 1 2 1 1 2 1 -1

如果区间[2,4]都加上3的话

数组变为

a 1 6 7 8 7 8 4
b 1 5 1 1 -1 1 -1

我们发现差分数组只有b[2]和b[5]发生了变化,因此我们只修改差分数组b[2]和b[5]即可

树状数组结构分析:

请添加图片描述

我们可以看到数组c的存放

c[1]=1;
c[2]=1+2;
c[3]=3;
c[4]=c[2]+c[3]=1+2+3+4;
c[5]=5;
c[6]=c[5]+6;
c[7]=7;
c[8]=c[4]+c[6]+c[7]=1+2+3+4+5+6+7+8;

如果我们想要得到数组c的值呢?

我们可以观察1都在哪个数组计算中出现过:c[1],c[2],c[4],c[8]…,下标的关系为i+=lowbit(i);

因此我们构造函数:

void add(int a,int b)//a为当前数字,b为数组数字个数
{
	for(int i=a;i<=b;i+=lowbit(i))
	{
		c[i]+=a;
	}
}

如果想求前缀和,我们再观察一下

sum[1]=c[1];
sum[2]=c[2];
sum[3]=c[3]+c[2];
sum[4]=c[4];
sum[5]=c[5]+c[4];
...

通过观察我们可以发现求和时,我们需要用到数组c的数量、下标也是有规律可循的,i-=lowbit(i);

int sum(int x)
{
	int res=0;
	for(int i=x;i>=1;i-=lowbit(i))
	{
		res+=c[i];
	}
	return res;
}

现在基本的函数都已经讲完了,那为什么这样的操作会让使得区间修改比较容易呢?

我们如果朴素做法,我们需要循环整个区间来依次操作,如果按照树状数组的话,我们将修改区间的数引用函数add(i,)则可达到修改数组c的作用,由于每次求和时我们引用数组c,因此也可以达到维护前缀和的目的

例题:

P3374 【模板】树状数组 1(原题链接

题意:

数列n个数,进行m次操作

操作1: 将某一个数加上x

操作2:求出某一区间每一个数的和

思路:
  1. 操作1可以直接引用我们上述函数add
  2. 操作2我们求出区间l-1,r的前缀和相减即可
代码:
#include<bits/stdc++.h>
using namespace std;
#define N 1000010
#define int long long
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define f(i,a,b) for(int i=a;i<=b;i++) 
int a[N],c[N]; 
int n,t;

int lowbit(int p)
{
	return (p&(-p));
}

void add(int x,int y)
{
	while(x<=n)
	{
		c[x]+=y;
		x+=lowbit(x);
	}
}

int query(int w)
{
	int ans=0;
	while(w)
	{
		ans+=c[w];
		w-=lowbit(w);
	}
	return ans;
}
signed main()
{
	IOS
	cin>>n>>t;
	f(i,1,n)
	{
		cin>>a[i];
		add(i,a[i]);
	}
	int op,u,v;

	f(i,1,t)
	{
		cin>>op>>u>>v;
		if(op==1)
		add(u,v);
		else
		cout<<query(v)-query(u-1)<<endl;
	}
	return 0;
}

P3368 【模板】树状数组 2(原题链接

题意:

数组n个数,m次操作

操作1:对某一区间每一个数都将上一个x

操作2:求出某一个数的值

思路:

这道题我们需要用到差分思想

我们此时数组c先按照最初的存放,当需要变换时,我们变换数组c区间l,r+1的值,当需要输出时,我们数组差分数组前缀和即为当前所求值

代码:
#include<bits/stdc++.h>
using namespace std;
#define N 1000010
#define int long long
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define f(i,a,b) for(int i=a;i<=b;i++) 
int a[N],c[N]; 
int n,t;

int lowbit(int p)
{
	return (p&(-p));
}

void add(int x,int y)
{
	while(x<=n)
	{
		c[x]+=y;
		x+=lowbit(x);
	}
}

int query(int w)
{
	int ans=0;
	while(w)
	{
		ans+=c[w];//寻找差分的标记
		w-=lowbit(w);
	}
	return ans;
}
signed main()
{
	IOS
	cin>>n>>t;
	f(i,1,n)
	{
		cin>>a[i];
		int x=a[i]-a[i-1];
		add(i,x);
	}
	int op;
	int u,v,r;
	f(i,1,t)
	{
		cin>>op;
		if(op==1)
		{
			cin>>u>>v>>r;
			add(u,r);
			add(v+1,-r);
		}
		else
		{
			cin>>u;
			cout<<query(u)<<endl;
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值