树状数组【Leo_Jose】

本文介绍了树状数组(也称线段树)的基础知识,包括位运算和前缀和等预备概念。详细阐述了树状数组的生成规则,如何通过计算机的思考方式快速计算末尾0个数,并探讨了快速相加的优化方法。文章还提到了树状数组在求解区间和问题中的应用,并提供了相关练习题目。
摘要由CSDN通过智能技术生成

前置知识

位运算(在学习这个算法之前,请务必熟练掌握位运算)
前缀和(利用了前缀和的思想,需要掌握前缀和思想)

解决问题

除主席树以外几乎所有问题都能够用树状数组解决,不过十分麻烦
树状数组模板所解决的问题如下:
给出一个n个数的数组和m次操作
每次操作可以将数组中的某个元素加上一个值
或者需要你输出某个区间的元素之和
在这里用LGOJ3374 【模板】树状数组1
来作为题面

算法详解

图丑请见谅
最下层是原数组,上面的长得像一颗树的东西就是树状数组
对于原数组a[]={1,2,3,4,5,6,7,8,9,10,11,12,13}
可以生成树状数组c[]={1,3,3,10,5,11,7,36,9,19,11,42,13}

生成树状数组的规则

将树状数组下标用二进制来表示出来就是:
0001,0010,0011,0100,0101,0110,0111,1000,1001,1010,1011,1100
树状数组生成规则1:
第一个数0001的末尾0个数为0,也就是没有末尾0,那么c[1]=a[1]
第二个数0010的末尾0个数为1,那么c[2]=a[1]+a[2]
第三个数0011的末尾0个数为0,那么c[3]=a[3]
第四个数0100的末尾0个数为2,那么c[4]=a[1]+a[2]+a[3]+a[4]
也就是树状数组中如果某一个下标为x,将x转化为二进制之后的末尾0个数为y
那么 c [ x ] = a [ x ] + a [ x − 1 ] + ⋯ + a [ x − 2 y + 1 ] c[x]=a[x]+a[x-1]+\dots+a[x-2^y+1] c[x]=a[x]+a[x1]++a[x2y+1]
这就是树状数组的生成规则

用计算机的思考方式来生成树状数组

末尾0个数

首先我们需要快速判断一个数转化成二进制的末尾0个数

如果你对位运算十分地熟悉的话,请将数值代入到下面的公式进行计算:
x&(-x)
你算完后会很神奇地发现这个公式的计算结果的末尾0个数,就是x转化为二进制后的末尾0的个数
如果x的末尾0个数为y的话,这个公式的计算结果就会是 2 y 2^y 2y
这个公式我们用lowbit()函数来表示

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

快速相加

我们现在已经知道了每个数的末尾0个数,但是不能说是某个数有4个末尾0,就真的把原数组中 2 4 = 16 2^4=16 24=16个元素全部算一遍,这样太浪费时间了
这个时候我们又需要用上lowbit()函数了
图丑请见谅
看看这个图片,你会发现c[4]所连接的元素,是c[2],c[3],a[4]三个元素,而不是a[1],a[2],a[3],a[4]四个元素
是因为c[3]的值就是a[1]+a[2]
所以我们只用调用c[3]一个值而不是a[1],a[2]两个值,这样我们就节省下来了时间
这个操作我们可以用lowbit()函数来实现

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

这个用语言不大好解释,需要自己跟着这个代码推演一边,只要认真推演了就可以完全理解并掌握这个数据结构的核心了。

前缀和

我们可以发现,其实树状数组的每一个元素都是原数组某一段区间的区间和
那么很多的区间凑到一起,就可以凑出一个原数组的前缀和来

int getsum(int x, int y)
{
	int t=0;
	while(x>0)
	{
		t+=c[x];
		x-=lowbit(x);
	}
	return t;
}

同样,自己跟着代码认认真真,仔仔细细地推演一遍

得到了前缀和,区间和也呼之欲出了

主程序

int main()
{
	cin>>n>>m;
	for(int i=1,x;i<=n;i++)
	{
		cin>>x;
		add(i,x);//在输入的时候就要边输入边生成树状数组
	}
	while(m--)
	{
		int cse,x,y;
		cin>>cse>>x>>y;
		if(cse==1)
			add(x,y);//单点修改
		else
			cout<<getsum(y)-getsum(x-1)<<endl//区间查询
	}
}

练习题

LGOJ2068 统计和
HDU1166 敌兵布阵

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值