树状数组——学习心得

可以解决大部分区间上面的修改以及查询的问题,例如1.单点修改,单点查询,2.区间修改,单点查询,3.区间查询,区间修改,换言之,线段树能解决的问题,树状数组大部分也可以,但是并不一定都能解决,因为线段树的扩展性比树状数组要强.

先简单了解一下树状数组的结构有个大致印像

树状数组很像前缀和结合树形结构的实现方式,对于t[x]来说是一种变异的前缀和,它所表示的是[x-len + 1, x]这个范围的和,而len就是上图左侧的标注的对应值。(len的代表的什么又怎么求下面会详细描述)

根据上面解释的t[x]的含义,想必善于观察的同学已经发现了t[x]在图中的特点,我们可以举个栗子来归纳一下

根据连线关系可以得到如下关系:

t[4] = t[2] + t[3] + a[4]

t[6] = t[5] + a[6]

t[8] = t[4] + t[6] + t[7] + a[8]

t[3] = a[3]

t[5] = a[5]

t[7] = a[7]

是不是规律有点呼之欲出的感觉,我们发现:

  • x为奇数时总等于a[x]

  • x为2的倍数时t[x]就为它的所有子节点(t[])加上a[x]的和。

所以我们想要快速求出t[x]就必须知道它有哪些子节点,这时候我们就需要引入一种特别的运算方法lowbit(x),我们就是通过lowbit来求len的值

lowbit(x)的描述如下:求的是x的二进制表示下的最低位的1和此位后的0组成的值,如x = 4(100)时得到的就是100(4);当x = 6(110)时得到的就是10(2);x = 7(111)时得到的就是1(1)

而求解方法也很简单就是x&-x(x按位与上取反后的x),假如x=4, 因为最低位1后面都是0,所以取反后最低位1会变0,后面的0都会变1(0100 --> 1011 ),然后再+1就得到了-x(1100),接下来按位与:

    0100

& 1100

= 0100

就得到最后我们要的值。(不明白的可以先看看负数的二进制表示和按位与运算)

最后我们知道 len = lowbit(x){return x&-x;}

对于sum[7] = t[4] + t[6] + t[7],而我们发现7-lowbit(7)=6,6-lowbit(6)=4,4-lowbit(4)=0,用x-lowbit(x)就能找到下一个需要的区间,同理x+lowbit(x)就能找到父节点。

到这里我们就可以总结一下t[x]的求法了

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

//求和
int getSum(int x) {//往前找
	int sum = 0;
	for(int i = x; i > 0; i -= lowbit(i)){
		sum += t[i];
	}
	return sum;
}

//更新t[]
void update(int x, int val) {//往后找,找父节点
	for(int i = x; i<= bound ; i += lowbit(i)) {
		t[i] += val;
	}
}

void init() {
    int a[] = {0,2,3,4,1,6,4,7,8};//从下标为1开始
    for(int i = 1; i < a.length; i++) {
        update(i, x);
    }
}



//1.单点修改,单点查询
int main() {
	init();
    //单点修改
    update(x,y);//下标为x的值改变y;
    //单点查询  
    getSum(k);
}

//2.区间修改,单点查询
int main() {
	init();
    //区间修改  [m,n]改变y
    for(int i = m; i <= n; i++) {
        update(i, y);
    }
    //单点查询  
    getSum(k);
}

//3.区间查询,区间修改
自己动手尝试一下吧

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值