我们也不难发现,这个k就是该节点在树中的高度,因而这个树的高度不会超过logn。
所以,当我们修改A[i]的值时,可以从C[i]往根节点一路上溯,调整这条路上的所有C[]即可,
这个操作的复杂度在最坏情况下就是树的高度即O(logn)。
另外,对于求数列的前n项和,只需找到n以前的所有最大子树,把其根节点的C加起来即可。
不难发现,这些子树的数目是n在二进制时1的个数,或者说是把n展开成2的幂方和时的项数,
因此,求和操作的复杂度也是O(logn)。
接着,我们考察这两种操作下标变化的规律:
首先看修改操作:
已知下标i,求其父节点的下标。
我们可以考虑对树从逻辑上转化:
如图,我们将子树向右对称翻折,虚拟出一些空白结点(图中白色),将原树转化成完全二叉树。
有图可知,对于节点i,其父节点的下标与翻折出的空白节点下标相同。
因而父节点下标 p=i+2^k (2^k是i用2的幂方和展开式中的最小幂,即i为根节点子树的规模)
即 p = i + i&(i^(i-1)) 。
接着对于求和操作:
因为每棵子树覆盖的范围都是2的幂,所以我们要求子树i的前一棵树,只需让i减去2的最小幂即可。
/*树状数组的缺点实不能求某一区间的最值
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;
......
c2^n=a1+a2+....+a2^n;
c[x]=a[x-2^k+1]+...+a[x];//总共2^k个;k为x的的二进制表示的末尾的0的个数
注:改变a[x]时只需改变c[x],c[x+lowbit(x)],c[x+lowbit(x)+lowbit(x+lowbit(x))]....;???????
*/
#include
#define N 100
int n;
int c[N];
int a[N];
int lowbit(int x)//)//返回x二进制最右边1;即t=2^k;k为x的的二进制表示的末尾的0的个数
//求一个数x的的二进制表示的末尾的0的个数2^k=x&(x^(x-1));
{
int lb;
return lb=x&(x^(x-1));//等价于lb=(-x)&x;
}
void change(int k,int delta)//注:这里的detal是指a[k]变化了detal;
{
//更新包含C[i]的所有C[k]???????
while(k
c[k]=c[k]+delta;
k=k+lowbit(k);
}
}
int getsum(int k)//返回sum[k]=(a[1]+...a[k]);即c[x]+c[x-lowbit(x)]+c[i-lowbit(x)-lowbit(x-lowbit(x))].....lowbit(x)=2^k;????????
{
int t=0;
while(k>0)
{
t=t+c[k];//C[i]=C[i-2^k+1]+...+C[i]
k=k-lowbit(k);
}
return t;
}
/*列:对a[]={2,5,3,4,1},求b[i]=位置i左边小于等于a[i]的数的个数;只需对任意
a[i]每次getsum(a[i]),再updeta(a[i],1)即可;
但是,如果求的是b[i]=位置i左边大于等于a[i]的数的个数;这要改变函数:update变为每次k-lowbit(k);而getsum变成k+lowbit(k),于前面正好相反(看图理解)
在数据范围较大时,应用离散化处理;a[]={1000000,10,2000,20,300};离散化后a[]={5,1,4,2,3}.
*/
/*二维树状数组
c[x][y]=sum(a[i][j]);
x-lowbit(x)+1<=i<=x,
y-lowbit(y)+1<=j<=y.
//修改a[x][y]
void change(int x,int y,int delta)
{
for(int i=x;i
}
//查询sum(a[1][1]+...a[i][j])
int getsum(int i,int j)
{
int result=0
for(int x=i;x>0;x-=lowebit(x))
for(int y=j;y>0;y-=lowebit(y))
result+=c[x][y];
return result;
}
*/