树状数组(区间维护/单点修改/区间最值)

1,定义

数组数组用于维护区间信息,简洁的几行的代码可以单点操作/区间查询,或者区间操作与单点查询。

注意:维护区间信息时,查询ask(x)返回的是x的值。进行的单点操作,查询ask(x)返回的是1~x的前缀和

虽然功能小于线段树,但是在相同功能的实现上,两者复杂度(但是线段树常数大)差不多。线段树

2,实现思路

树状数组有两个功能,一个是单点修改,区间(单点)查询。一个是区间修改,但是只能单点查询。两个功能不能同时使用。

我们先介绍第一个功能

先看一下树状数组的图

树状数组(区间维护/单点修改/区间最值)_第1张图片

 我们观察出他的几个性质:

  1. 最外层的t是2的倍数(只有一个1),而且他的t[x]的值是a[1]+...+a[x]
  2. 每个点的父亲都是该点加上自己的最低位1就可得到如0010,最低位是10,0010+10=0100,就是父亲。
  3. 我们求x的1到x的前缀和,假设x=7,那么sum[7]=t[7]+t[6]+t[4],发现每次都是不断减去最低位1,直到最外层,即7-lowbit(7)=6,   6-lowbit(6)=4。

我们如何求最低位1呢?

  1. 我们知道:-x等价于x取反+1,假设x=10110,~x=01001,~x+1=01110。注意到-x与x的最低位1同位,其他高位全部不同,这是偶然吗?
  2. 我们对x取反,那么~x与x所有位不同,但是加上1后,再遇到最低位第一个0前,1全部变成0,不断往前推进(使得本来不同变得相同),当遇到0后,1被0吸收变成1,不再往后了,那么后面的高位仍然保持所有位不同。这样,我们把-x&x得到的就是lowbit,即最低位1

1,单点操作如何如何区间值更新呢

假如我们对x+k,那么他的祖先们都需要,去祖先的更新方法就是+lowbit

void add(int x,int k)
{
	for (int i=x; i<=n; i+=i&-i)t[i]+=k;
}

2,区间查询思路简单,我们t表示的是子树的值

那么我们从t[x]一直减最低位1,累加到t[0]即可

ll ask(int x)
{
	ll ans=0;
	for (int i=x; i; i-=i&-i)ans+=t[i];
	return ans;
}

模板题:P3374 【模板】树状数组 1

#include 
using namespace std;
#define ll     long long
const int N = 5e5 + 10;
int a[N],t[N];
int n;
void add(int x,int k)
{
	for (int i=x; i<=n; i+=i&-i)t[i]+=k;
}

ll ask(int x)
{
	ll ans=0;
	for (int i=x; i; i-=i&-i)ans+=t[i];
	return ans;
}

int main()
{
	int m,p,x,y;
	cin>>n>>m;
	for (int i=1; i<=n; ++i)cin>>a[i],add(i,a[i]);
	while (m--)
		{
			cin>>p>>x>>y;
			if (p==1)
				{
					add(x,y);
				}
			else if (p==2)
				{
					cout<

3,实现功能2

  1. 我们这时只维护单点的值,不再是区间值,所以思路是把原来的数组表示为差分数组,那么原来的x的前缀和就成为x的值

代码没什么区别

int b[N];
int n;
void add(int x,int k)
{
	for (int i=x; i<=n; i+=i&-i)b[i]+=k;
}
ll ask(int x)
{
	ll ans=0;
	for (int i=x; i; i-=i&-i)ans+=b[i];
	return ans;
}

模板题2:P3368 【模板】树状数组 2

#include 
using namespace std;
#define ll long long
const int N = 5e5 + 10;

int b[N];
int n;
void add(int x,int k)
{
	for (int i=x; i<=n; i+=i&-i)b[i]+=k;
}
ll ask(int x)
{
	ll ans=0;
	for (int i=x; i; i-=i&-i)ans+=b[i];
	return ans;
}

int main()
{
	int m,p,x,y,k;
	cin>>n>>m;
	for (int i=1; i<=n; ++i)cin>>x,add(i,x),add(i+1,-x);
	while (m--)
		{
			cin>>p;
			if (p==1)
				{
					cin>>x>>y>>k;
					add(x,k),add(y+1,-k);
				}
			else if (p==2)
				{
					cin>>x;
					cout<

4,维护区间最值

  1. 我们联想线段树是如何维护区间最值的——由子代区间最值更新到父代。树状数组也是树形结构,我们可以把t[]数组表示为他包含区间(看一开始那张图)的最值。设a[]为原数组,t[]为区间最值数组(显然由树状数组的结构可以看出他包含的区间就是[x-lowbit[x]+1,x]
  2. 那么,比如我们求[2,7]的最值
    1. 从7开始下降遍历子树,首先7没有儿子,那么先取t[7],7-lowbit(7)=6
    2. t[6]维护[5,6]最值,所以取t[6],6-lowbit[6]=4
    3. t[4]维护[1,4]超出区间,那么先取a[4],然后寻找符合访问的4的亲儿子。
    4. t[3]是4的儿子,维护3最大值,取
    5. t[2]是4儿子,超出范围,取a[2],t[2]儿子t[1]不符合范围,舍去
    6. 所以结果就是max{t[7],t[6],a[4],a[3],a[2]}
  3. 如何区间修改呢
    1. 所以我们修改一个点x为w时,先改t[x]=a[x]=w,然后遍历t[x]的亲儿子们更新t[x]区域的最大值。
    2. 如何遍历t[x]的亲儿子呢?观察到儿子们就是儿子v加上lowbit(v)=x,那么反过来想,x得到他们就是对x小于lowbit(x)的二进制位上-1,设lowbit(x)=(100)_{2}=4,那么x的儿子就是x-2^1与x-2^2即(011)与(010)
    3. 显然这还不够,t[x]也是某人的儿子啊,那么儿子变了,父亲也需要变。我们是需要重新遍历父节点的所有亲儿子的(否则有可能无法成功修改父节点最大值,如原本t[父]=t[x](原来的),而t[x]修改后变小了,显然只有重新让父节点遍历所有儿子才能重新修改)
  4. 如何区间查询呢,其实就是按照刚才遍历的思路寻找即可。

模板题:Problem - 1754 (hdu.edu.cn)

#include 
using namespace std;
#define ll               long long
#define endl             "\n"
#define int              long long
const int N = 2e5 + 10;

int t[N],a[N];
int n,q;
void update(int x,int w)
{
	t[x]=a[x]=w;//修改时该点也要修改
	while(x<=n)//遍历x及其所有树上祖先进行修改
		{
			int lx=x&-x;//lowbit(x)
			for(int i=1; i=l)ans=max(ans,t[r]),r=lx-1;//查询时,如果[x-lowbit[x]+1,x]在范围内,直接获取,否则,取出a[x],x--
			else ans=max(ans,a[r]),r--;
		}
	return ans;
}

void mysolve()
{

	while(cin>>n>>q)
		{
			for(int i=1; i<=n; ++i)cin>>a[i],update(i,a[i]);
			int x,y;
			char c;
			while(q--)
				{
					cin>>c>>x>>y;
					if(c=='Q')cout<> t;
	while (t--)
		{
			mysolve();
		}
	system("pause");
	return 0;
}

你可能感兴趣的:(数据结构,算法)