树状数组

树状数组是一种支持单点修改和区间查询的数据结构,

结构如下(用二进制表示,方便查看):

å¨è¿éæå¥å¾çæè¿°

 先定义一个函数lowbit(i)这个函数返回的值就是i的二进制下从右到左的第一个1保留其他全部变为0所对应的数值,举个例子:14的二进制1110,我们把从右到左的第一个1保留,其他全变为0就是0010,值也就是2,我们发现如果i+lowbit(i)得到的数就是它父亲的位置,i-lowbit(i)就可以找到他的儿子的位置,有什么用呢?这样我们就可以从父亲到儿子,也可以从儿子到兄弟。
 

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

 对于操作: x & -x, 其-x 在计算机存储是用x的补码存储即,~x + 1

构树:对于树状数组从二进制角度看,设a为题目给的数列,c是我们的树状数组。对于c中的每一个我们可以看成一个树的节点,而对应二进制的拆分以及a中对应的数(例如c[16(10000)]就可以拆成c[8(1000)]、c[12(1100)]、c[14(1110)]、c[15(1111)]以及a[16(10000)])拆分是怎么分的?其实很简单对于一个数,从右往左找到第一个1,把这个1变成0,然后这个位置右边的0不断地依次变成1,这样就可以构成树了。(这种理解方法很好,感谢dalao),空间是O(n);

查询:我们可以发现要查询[1,i]([i,j]可以看成[1,j]-[1,i-1])有两种情况,一,i刚好是2的次方倍,直接输出;二不是2的次方倍,那该怎么办呢?我们根据上面的图发现其实就是i以及比i小的兄弟的和,那么我们直接根据lowbit函数找到兄弟。

int query(int i)//查询区间【1,i】的和
{
	int ans=0;
	while(i>0)
	{
		ans+=c[i];
		i-=lowbit(i);
	}
	return ans;
}

修改:我们如果改动一个数的值,根据图我们可以发现影响到的一定只有它的祖先,所以我们只要修改它本身和祖先就好了。

修改和建树是一起的,初始化为0,每次输入当作修改处理就行。

void change(int i,int num)//修改
{
	while(i<=n)
	{
		c[i]+=num;
		i+=lowbit(i);
	}
	return ;
}

洛谷上的一道板子题:

区间查询、单点修改

#include<iostream>
#include<cstdio>
using namespace std;
int c[500100],n;
int lowbit(int i)
{
	return i&(-i);
}
void change(int i,int num)//修改
{
	while(i<=n)
	{
		c[i]+=num;
		i+=lowbit(i);
	}
	return ;
}
int query(int i)//查询
{
	int ans=0;
	while(i>0)
	{
		ans+=c[i];
		i-=lowbit(i);
	}
	return ans;
}
int main()
{
    ios::sync_with_stdio(false);
	int m;
	cin>>n>>m;
	for(int i=1;i<=n;i++)//输入数据
	{
		int x;
		cin>>x;
		change(i,x);//直接当成修改处理
	}
	for(int i=1;i<=m;i++)
	{
		int b,x,k;
		cin>>b>>x>>k;//输入查询或修改
		if(b==1)
			change(x,k);//修改
		else
		{
			int ans;
			ans=query(k)-query(x-1);//查询
			cout<<ans<<endl;
		}
	}
	return 0;
 }

 区间修改单点查询

那区间修改和单点查询呢?可以用树状数组维护差分,我们新增一个数组a表示修改的差分然后直接在(修改范围是[i,j])i上加k(k是修改值),j-1上加-k这样就修改好了,查询就是差分的前缀和加上自己本身的值。

#include<iostream>
#include<cstdio>
using namespace std;
int c[500100],n,b[500100];
int lowbit(int i)
{
	return i&(-i);
}
void change(int i,int num)//修改
{
	while(i<=n)
	{
		c[i]+=num;
		i+=lowbit(i);
	}
	return ;
}
int query(int i)//查询
{
	int ans=0;
	while(i>0)
	{
		ans+=c[i];
		i-=lowbit(i);
	}
	return ans;
}
int main()
{
    ios::sync_with_stdio(false);
	int m;
   cin>>n>>m;
   for(int i=1;i<=n;i++)
   {
   	cin>>b[i];//输入数据
   }
   for(int i=1;i<=m;i++)
   {
   	int d,x,y,k;
   	cin>>d;
   	if(d==1)
   	{
   		cin>>x>>y>>k;//修改区间
   		change(x,k);
   		change(y+1,-k);
   	}
   	else
   	{
   		cin>>x;
   		int ans;
   		ans=query(x);//单点查询
   		cout<<ans+b[x]<<endl;;
   	}
   }
   return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值