树状数组:二进制的应用
与线段树的区别:树状数组的问题都可以用线段树解决,树状数组系数少,效率高
修改、查询复杂度 :O(log N)

单点更新、区间查询:
C[1]=C[0001]=A[1]
C[2]=C[0010]=A[1]+A[2]
C[3]=C[0011]=A[3]
C[4]=C[0100]=A[1]+A[2]+A[3]+A[4]
C[5]=C[0101]=A[5]
C[6]=C[0110]=A[5]+A[6]
C[7]=C[0111]=A[7]
C[8]=C[1000]=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:二进制中最低位到高位连续零的长度
Sum(i)=C[i]+C[i-2^k1]+C[(i-2^k1)-2^k2)+...
A[i] 包含于 C[i + 2k]、C[(i + 2k) + 2k]...
例如求前7项:C[7]+C[6]+C[4]
Sum[7]=sum[111]=C(100)+C(110)+C(111)
lowbit:取2^k
int lowbit(int x) {
return x&(-x);
}
更新函数
void updata(int x,int y) {
for(int i=x;i<=n;i+=lowbit(i))
c[i]+=y;
}
求和函数
int getsum(int x) {
int ans=0;
for(int i=x;i>0;i-=lowbit(i)) ans+=c[i];
return ans;
}
例题:http://acm.hdu.edu.cn/showproblem.php?pid=1166

#include <bits/stdc++.h>
using namespace std;
int n,m;
int a[50005],c[50005];
int lowbit(int x) {
return x&(-x);
}
void updata(int x,int y) {
for(int i=x;i<=n;i+=lowbit(i))
c[i]+=y;
}
int getsum(int x) {
int ans=0;
for(int i=x;i>0;i-=lowbit(i)) ans+=c[i];
return ans;
}
int main( ) {
int t;
cin>>t;
int tot=1;
while(t--) {
cout << "Case " << tot++ << ":" << endl;
memset(a, 0, sizeof a);
memset(c, 0, sizeof c);
cin>>n;
for(int i = 1; i <= n; i++){
cin>>a[i];
updata(i,a[i]);
}
string s;
int x,y;
while(cin>>s && s[0] != 'E') {
cin>>x>>y;
if(s[0] == 'Q') {
int sum = getsum(y) - getsum(x-1);
cout << sum << endl;
}
else if(s[0] == 'A') updata(x,y);
else if(s[0] == 'S') updata(x,-y);
}
}
return 0;
}
区间更新、单点查询:
把a-b区间内所有值全部加上k或者减去k
用传统树状数组复杂度不允许,不能再用数据的值建树
引入差分,利用差分建树
C[i]=a[i]-a[i-1] a为原数组
当某个区间[x,y]的值改变,区间内的差值不变
只有C[x]和C[y+1]的值改变了
对C[]数组建立树状数组
A[i]=Σ(i,j=1)C[i] 前面i项的差值和
例如:
A[0]=0;
A[]=1 2 3 5 6 9
C[]=1 1 1 2 1 3
把区间[3,5]加上2
A[]=1 2 5 7 8 9
C[]=1 1 3 2 1 1
C[3]的差值与C[5+1]的差值发生了改变
对C[]数组建立树状数组
例题:https://www.luogu.org/problem/P3368

#include <iostream>
using namespace std;
typedef long long ll;
int a[500007],c[500007];
int n,m;
int lowbit(int x) {
return x&(-x);
}
void update(int x,int y) {
for(int i=x;i<=n;i+=lowbit(i)) c[i]+=y;
}
int getsum(int x) {
int ans=0;
for(int i=x;i>0;i-=lowbit(i)) ans+=c[i];
return ans;
}
int main( ) {
scanf("%d %d",&n,&m);
a[0]=0;
for(int i=1;i<=n;i++) {
scanf("%d",&a[i]);
update(i,a[i]-a[i-1]);
}
for(int i=1;i<=m;i++) {
int flag,x,y;
int k;
scanf("%d",&flag);
if(flag==1) {
scanf("%d %d %d",&x,&y,&k);
update(x,k);
update(y+1,-k);
}
else {
scanf("%d",&x);
printf("%d\n",getsum(x));
}
}
return 0;
}
区间更新、区间查询
还是利用差分
Σ(n,i=1)A[i]=Σ(n,i=1)Σ(i,j=1)D[j]
即:
A[1]+A[2]+...+A[n]
=(D[1])+(D[1]+D[2])+...+(D[1]+D[2]+..+D[n])
=n*D[1]+(n-1)*D[2]+...+D[n]
=n*(D[1]+D[2]+...+D[n])-(0*D[1]+1*D[2]+...+(n-1)*D[n])
所以: Σ(n,i=1)A[i]=n*Σ(n,i=1)D[i] - Σ(n,i=1)(D[i]*(i-1));
维护两个树状数组
A[i]=D[i]
B[i]=D[i]*(i-1);
例题:https://vjudge.net/problem/POJ-3468

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
ll a[500007],c[500007];
ll A[500007],B[500007];
int n,m;
int lowbit(int x) {
return x&(-x);
}
void update(ll x,ll y) {
ll k=x;
for(int i=x;i<=n;i+=lowbit(i)) {
A[i]+=y;
B[i]+=y*(k-1);
}
}
ll getsum(ll x) {
ll ans=0;
ll k=x;
for(int i=x;i>0;i-=lowbit(i)) ans+=k*A[i]-B[i];
return ans;
}
int main( ) {
scanf("%lld %lld",&n,&m);
a[0]=0;
for(int i=1;i<=n;i++) {
scanf("%lld",&a[i]);
update(i,a[i]-a[i-1]);
}
for(int i=1;i<=m;i++) {
char s;
cin>>s;
ll x,y,k;
if(s=='Q') {
scanf("%lld %lld",&x,&y);
cout<<getsum(y)-getsum(x-1)<<endl;
}
else {
scanf("%lld %lld %lld",&x,&y,&k);
update(x,k);
update(y+1,-k);
}
}
return 0;
}
树状数组还可以求逆序对、区间最大值,这些以后再更新了
