1.先行知识
首先介绍lowbit(x)函数
lowbit(x)函数:lowbit(i)的意思是将i转化成二进制数之后,只保留最低位的1及其后面的0,截断前面的内容,然后再转成10进制数比如lowbit(7),7的二进制位是111,lowbit(7) = 1,6 = 110(2),lowbit(6) = 2,同理lowbit(4) = 4,lowbit(12) = 4,lowbit(2) = 2,lowbit(8) = 8。
具体计算方法:lowbit(x)=x&(-x);
我们知道,计算机在进行运算的时候采用的是补码,这里我们先来介绍一下原码,补码,和反码;
原码
原码就是符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值. 比如如果是8位二进制:
[+1]原 = 0000 0001;[-1]原=1000 0001;
第一位是符号位. 因为第一位是符号位, 所以8位二进制数的取值范围就是:1111 1111 ~0111 1111,即-127~127,原码是人脑最容易理解和计算的方式。
反码
反码的表示方法:正数的反码是本身,负数的反码在器原码的基础上,符号位不变,其余各位取反。
[+1]原 = [0000 0001]原=[00000001]反
[-1]原=[1000 0001]原=[1111 1110]反
补码
补码的表示方法:正数的补码就是其本身,负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1)
[+1]原 = [0000 0001]原=[00000001]补
[-1]原=[1000 0001]原=[1111 1111]反
现在窝萌可以来用计算方法中的公式来计算lowbit(x)的值了,现在我们计算lowbit(12)的值,12的二进制数表示为0000 1100,由文章开头的那种做法我们可以得到lowbit(12)=100(2),即为4,现在我们使用x&(-x)来计算一下,12的补码为0000 1100,-12补码为1111 0100,对这两个二进制数求与运算,得0000 0100,和第一种计算的方法得出的结果是相同的。
求lowbit(x)的代码:
1 int lowbit(x) 2 { 3 return x & -x; 4 }
2.树状数组
首先我们搞明白树状数组是用来干嘛的,现在有一个这样的问题:有一个数组a,下标从0到n-1,现在给你w次修改,q次查询,修改的话是修改数组中某一个元素的值;查询的话是查询数组中任意一个区间的和,w + q < 500000。
这个问题很常见,首先分析下朴素做法的时间复杂度,修改是O(1)的时间复杂度,而查询的话是O(n)的复杂度,总体时间复杂度为O(qn);可能你会想到前缀和来优化这个查询,我们也来分析下,查询的话是O(1)的复杂度,而修改的时候修改一个点,那么在之后的所有前缀和都要更新,所以修改的时间复杂度是O(n),总体时间复杂度还是O(qn)O。
可以发现,两种做法中,要么查询是O(1),修改是O(n);要么修改是O(1),查询是O(n)。那么就有没有一种做法可以综合一下这两种朴素做法,然后整体时间复杂度可以降一个数量级呢?有的,对,就是树状数组。