1,首先它长这个样子:
由上图清楚看见:
C1=A1 ;C2=A1+A2;C3=A3;C4=A1+A2+A3+A4;.....依次类推
2,关于lowbit(int x)return x&(-x);
我们从图上可以看出规律:Ci=Ai+Ai-1....(2^k个A相加,这里k就是i二进制末尾0的个数);
比如说奇数(1,3,5....)二进制末尾都是0个0,所以Ci=2^0个A相加的结果;
比如8(二进制1000)所以是8个A相加;
根据反码的性质lowbit返回的就是2^k;
3,关于修改
int change(int x,int c){
for(;x<=N;x+=x&(-x))C[x]+=c;//N是右端点,c加上数值
}
/*
更新一个点;我们也需要更新它的根节点;
比如:更新3,lowbit(3)=1是3的子节点数目,3+lowbit(3)就相当于3+右兄弟所有节点数==根节点了。
所以他是更新3再更新4,4的lowbit为4,更新8,最后结束循环(看图)
*/
4,关于查询
int getsun(int x){
int re=0;
for(;x;x-=x&(-x))re+=C[x];
return re=0;
}
/*
对比修改,就是其逆过程;
查询6,就是求1-6;
先re+C[6],再加上C[4]结束循环(看图);
这个过程就是二进制最后一个1补为0的过程,所以时间复杂度就变logn了
*/
5,求区间和[l,r],就是利用前缀和的思想C[r]-C[l-1];
6,树状数组中的差分约束
比如说有这样的题目1,2....n的序列,有两种操作
A:a,b表示[a,b]间序号都要涂一次色;
B,a,问a图了几次色
这个题更新时change(a,1),change(b+1,-1);然后直接查询getsum(a);返回的就是单点信息
7,二维树状数组
C11=A11; C12=A11+A12;C13=A13;C14=A11+A12+A13+A14;
C12=A11+A21 A12=A11+A12+A11+A12+A21+A22;.........
和一维一样
void change(int x,int y,int c){//修改
for(int i=x;i<=N;i+=i&(-x)){
for(int j=y;j<=n;j+=j&(-j)){
C[i][j]+=c;
}
}
}
int getsum(int x,int y){//查询
int re=0;
for(int i=x;i;i-=i&(-x)){
for(int j=y;j;j-=j&(-j)){
re+=C[i][j];
}
}
return re;
}
8,区间最值的维护(末端插入端点)
8.1加入一个新节点,就要更新以该节点为根节点的树的最值。
比如加入节点7,lowbit(7)=1;C[7]=a[7]//a[7]为节点的数值;
比如加入节点8,C[8]为其下的所有节点数值=max(a[8],C7[],c[6],c[4]),lowbit(8)=8;
其直接连的节点个数分别是1(节点7),2(5,6)4(1,2,3,4);
比如16,直接连的个数是1,2,4,8;
所以我们更新的时候可以这样写:
void change(int r){//右端点,比如8
C[r]=a[r];//a[r]加进来的值 ,a[8]
for(i=1;i<lowbit(r);i<<=1){ //i=1,2,4;lowbit(8)=8
C[r]=max(C[n],C[r-i]); //max(a[8],c[8-1],C[8-2],C[8-4])
}
}
8,2查询区间[l,r]的最值
int query(int l,int r){
int re=a[r];
while(l<=r){
re=max(re,a[r]);
for(--r;r-l>=lowbit(r);r-=lowbit(r)){
re=max(re,C[r]);
}
}
return re;
}
--r是右其有起第二个节点么如区间7-8 re=a[8]了,接下来就看7了,7-7<lowbit(7),所以这就是while里面还要放一个
re=max(re,re[7])的理由了。
解释for循环,前面已经很多次解释lowbit(i),就是它应该覆盖的节点数,如果是r-l>=lowbit(r)大于等于右端点的覆盖了
那比较C[r]就是了,再缩小范围。