【算法速成】树状数组

此为算法速成blog,因此会略去一些推导细节。

简介:

树状数组是一个**查询和插入时间复杂度都在O(logn)**的数据结构。

应用:

主要用于查询区间和或者前n项和,但**每次只能修改一个元素的值。**经过简单修改可以在log(n)的复杂度下进行范围修改,但是这时只能查询其中一个元素的值(如果加入多个辅助数组则可以实现区间修改与区间查询)。

概念:

树状图
来观察这个图:
令这棵树的结点编号为C1,C2…Cn. Ci的值为子树叶子结点的权值总和,那么容易发现:
C1 = A1
C2 = A1 + A2
C3 = A3
C4 = A1 + A2 + A3 + A4
C5 = A5
C6 = A5 + A6
C7 = A7
C8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8

为什么要这样设计这棵树呢?答:这样设计可以利用二分来操作。
以下是百度百科的解释,由于我们是速成,可以暂时不用去理解。

答案是,这样会使操作更简单!看到这相信有些人就有些感觉了,为什么复杂度被log了呢?可以看到,C8可以看作A1~A8的左半边和+右半边和,而其中左半边和是确定的C4,右半边其实也是同样的规则把A5~A8一分为二……继续下去都是一分为二直到不能分树状数组巧妙地利用了二分,树状数组并不神秘,关键是巧妙!

那么现在,我们来通过A求C.
这里给出一条公式: C[i]=A[i-2k+1]+A[i-2k+2]+…A[i] k为i二进制位的末尾0长度, i >= 1。
这个公式什么意思呢?它其实是说明了C[i]的管辖范围在[i-2k+1 , i ]。
比如说,i = 8,二进制为1000,k = 3,2k= 8,那么c[8]就是A[1]…A[8]的权值总和。
i = 7,二进制为111,k = 0, 2k = 0,那么c[7]就是A[7].

下面给出计算2k的方法。

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

至于为什么是这样,我现在也不太清楚。大家可以查看其他的blog. 我们现在是速成,这个公式直接记住就可以。

有了上面的函数,我们的公式可以改写成C[i] = A[i-lowbit(i)+1] + A[i-lowbit(i)+2] +…+A[i];
也就是C[i]的管辖范围在[ i-lowbit(i)-1, i ].

ok,现在你已经知道C[i]怎么算了。那么回到应用,如何利用C[i]计算区间和或者前n项和呢?下面先给出代码。

int sum(int k)	//前k个数的和 
{
    int ans = 0;
    while(k)
    {
        ans += t[k];
        k -= lowbit(k);
    }
    return ans;
}

拿k = 6举例子,C4 = A1 + A2 + A3 + A4, C6 = A5 + A6,那么sum = C4 + C6;
i = 6,lowbit = 2;i = 4,lowbit = 4;
因此可以发现,传进去的参数k表示从1到k的整个区域,lowbit(i)表示C[i]的管辖范围,实际上是将一段段的C全部加起来。

那么如何求区间和呢?下面直接上代码。

int ask(int l,int r) //求l ~ r区间和 
{
    return sum(r) - sum(l-1); 
}

其实就是后面的前n项和与前面的前n项和相减,就可以得到中间的区间和了嘛。

那么当改变一个A[i]的值之后,怎么更新C[i]呢?

void change(int x,int p)//将第x个数加p 
{
    while(x <= n)
    {
        t[x] += p;
        x += lowbit(x);
    }
    return;
}

从第x个数开始,当前的C[x]加上p,然后跳到下一个与A[x]有关的C (x + lowbit(x)),也加上p. 实际上就是将范围扩大,把与A[x]有关的C全部都加上p.

总代码:

#include <iostream>
#define MAX 10010
using namespace std;

int t[MAX], a[MAX];
int n;

int lowbit(int x)	//求C管辖范围 2^k 
{
	return x & -x;
}

int sum(int k)	//前k个数的和 
{
    int ans = 0;
    while(k)
    {
        ans += t[k];	//将每段C都加起来。 
        k -= lowbit(k);	//减去C[k]的管辖区域 
    }
    return ans;
}

int ask(int l,int r) //求l ~ r区间和 
{
    return sum(r) - sum(l-1); 
}

void change(int x,int p)//将第x个数加p 
{
    while(x <= n)
    {
        t[x] += p;
        x += lowbit(x);
    }
    return;
}

int main()
{
	cin>>n;
	for(int i = 1;i <= n;i++)
	{
		a[i] = i;
		change(a[i], i);
	}
	int l ,r;
	while(cin>>l>>r)
	{
		cout<<ask(l,r)<<endl;
	}
	return 0;
}

相关资料:
百度百科“树状数组”

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值