树状数组及相关问题

194 阅读3分钟

这是我参与更文挑战的第6天,活动详情查看: 更文挑战


树状数组 ,顾名思义就是像树一样的数组,当然这里说的是存储方式像树,就像哈希散列表根据哈希函数来储存。

具体怎么存呢。定义每一列的顶端结点C[]数组 

image.png

图中C[i]代表 子树的叶子结点的权值之和 //这里以维护区间和为例。

C[1]=A[1];

C[2]=A[1]+A[2];

C[3]=A[3];

C[4]=A[1]+A[2]+A[3]+A[4];

C[5]=A[5];

C[6]=A[5]+A[6];

C[7]=A[7];

C[8]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8];

这分别是C[i]代表的意义。我们其实就是把区间和存在一颗满二叉树上,为什么要这么存呢

这就要引入lowbit函数了。

lowbit 函数 int lowbit(int x) {return x&(-x);} #define lowbit(x) x&(-x) ///宏定义形式 lowbit函数可以说是树状数组的精髓和核心了吧。它的作用是“二进制数从低位向高位数,第一个数字1"作为运算结果。比较难理解,看下例子好了:lowbit(12) = 4,12的二进制为1100,从低位到高位,碰见第一个1时,截断,只取下面部分作为结果,即:100,十进制是4。 这就体现了,二进制和位运算的奇妙之处了。刚开始接触,可能很难接受和理解。但是慢慢就懂了。

现在知道了这个函数,再看看下面这些,就应该知道为什么用满二叉树来储存了吧

将C[]数组的结点序号转化为二进制

1=(001)      C[1]=A[1];

2=(010)      C[2]=A[1]+A[2];

3=(011)      C[3]=A[3];

4=(100)      C[4]=A[1]+A[2]+A[3]+A[4];

5=(101)      C[5]=A[5];

6=(110)      C[6]=A[5]+A[6];

7=(111)      C[7]=A[7];

8=(1000)    C[8]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8];

对照式子可以发现  C[i]=A[i-2^k+1]+A[i-2^k+2]+......A[i]; (k为i的二进制中从最低位到高位连续零的长度)例如i=8时,k=3;

 

主要应用        区间查询: 由于我们的C[i]储存的[ i,i-lowbit(i)+1 ] 区间里每个数的和,为了方便求某段区间和,我们用一个数组sum [ x ]来存

区间 [ 1, x ]的和,由于[ x, y ]=[1, y] - [1 , x]  我们就能很方便得出任一区间的和了。

这里给出求Sum[ ] 的函数。

int sum(int c[],int i){
    int s=0;
    while(i>0){
        s+=c[i];
        i-=lowbit(i);
    }
    return s;
}

单点更新后的区间维护; 直接上代码吧,不难理解。

void updata(int i, int val)
{
	while (i <= n)
	{
		c[i] += val;
		i += lowbit(i);
	}
}

现在讨论一下区间最值查询和维护。区间最值和区间和虽然不一样,但思想上大致相同。

在求区间最值的算法中,用h[x]来储存[x,x-lowbit(x)+1]中每个数的最大值。

求区间最值的算法中还有一个a[i]数组,表示第i个数是多少。

 

区间最值的查询:没办法照搬区间和的方法,

假设query(x,y)为[ x , y ]上的最大值, 上面也可以知道h[ y] 代表[y,y-lowbit(y)+1]的最大值。

那么 

         若y-lowbit(y) > x ,则query(x,y) = max( h[y] , query(x, y-lowbit(y)) );

         若y-lowbit(y) <=x,则query(x,y) = max( a[y] , query(x, y-1);

用递归的思想来求解,代码中,我们常常用递推来求解。理解了上面这两个式子,基本上就没有问题了。

int query(int x, int y)
{
	int ans = 0;
	while (y >= x)
	{
		ans = max(a[y], ans);
		y --;
		for (; y-lowbit(y) >= x; y -= lowbit(y))
			ans = max(h[y], ans);
	}
	return ans;
}

更新后的区间维护

void updata(int x,int data)
{
	
	while (x <= n)
	{
		h[x] = data;//从左边开始维护
		for (int i=1; i<lowbit(x); i<<=1)
			h[x] = max(h[x], h[x-i]);
		x += lowbit(x);
	}		
}