二维树状数组
基础知识
若将树状数组与前缀和对应起来,那么二维树状数组就可以与二维前缀和对应起来。此时循环不适合以while的形式出现,而适合以for的嵌套的形式出现。单点修改与区间查询的顺序还是一样。
模板
int lowbit ( int x )
{
return x&(-x);
}
void update( int x , int y , int delta )
{
for ( int i=x;i<=N;i+=lowbit(i))
for ( int j=y;j<=N;j+=lowbit(j))
Fenwick_Tree[i][j]+=delta;
}
int query( int x , int y )
{
int Ans=0;
for ( int i=x;i>0;i-=lowbit(i))
for ( int j=y;j>0;j-=lowbit(j))
Ans+=Fenwick_Tree[i][j];
return Ans;
}
应用
POJ1195 IOI2001 Mobile Phones
裸题。
代码
#include <stdio.h>
#include <string.h>
int Fenwick_tree[1050][1050],N=5;
int lowbit ( int x )
{
return x&(-x);
}
void update( int x , int y , int delta)
{
for ( int i=x;i<=N;i+=lowbit(i))
for ( int j=y;j<=N;j+=lowbit(j))
Fenwick_tree[i][j]+=delta;
}
int query( int x , int y )
{
int Ans=0;
for ( int i=x;i>0;i-=lowbit(i))
for ( int j=y;j>0;j-=lowbit(j))
Ans+=Fenwick_tree[i][j];
return Ans;
}
int main()
{
int Oper,x,y,a,x1,x2,y1,y2;
memset(Fenwick_tree,0,sizeof(Fenwick_tree));
while(scanf("%d",&Oper)!=EOF)
{
if(Oper==0)
{
scanf("%d",&N);
N++;
}
else if(Oper==1)
{
scanf("%d%d%d",&x,&y,&a);
x++,y++;
update(x,y,a);
}
else if(Oper==2)
{
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
x2++,y2++;
printf("%d\n",query(x2,y2)+query(x1,y1)-query(x1,y2)-query(x2,y1));
}
else if(Oper==3)
break;
}
return 0;
}
POJ2155 Matrix 楼天城男人八题
做法
TODO
代码
TODO
区间修改
做法
维护一个增量数组
delta
,
deltax
代表
[1,X]
上的区间增量。在对
[L,R]
进行区间修改时,我们将
[1,L]
区间减增量,即
deltaL
减去相应增量,而
[1,R]
区间增增量,即
deltaR
增加相应增量。最终求区间和时,有:
用递推可以维护 Ai ,那么用树状数组维护 deltai 和 deltai∗i 的前缀和即可。
例题
CodeVS1082 线段树练习3
题解
不解释
代码
#include <stdio.h>
long long Fenwick_Tree1[200001],Fenwick_Tree2[200001],A[200001],N;
long long lowbit(long long x)
{
return x&(-x);
}
void update( long long i , long long x , long long* target )
{
while(i<=N)
{
target[i]+=x;
i+=lowbit(i);
}
return ;
}
long long query ( long long i , long long *target)
{
long long Ans=0;
while(i>0)
{
Ans+=target[i];
i-=lowbit(i);
}
return Ans;
}
long long in()
{
long long Ans=0;
char ch=getchar();
char bi=1;
while(ch>'9'||ch<'0') {
ch=getchar();
if(ch=='-') bi=-1;
}
while(ch>='0'&&ch<='9'){
Ans=Ans*10+ch-'0';
ch=getchar();
}
return bi*Ans;
}
int main()
{
N=in();
for ( int i=1;i<=N;i++)
{
A[i]=in();
A[i]+=A[i-1];
}
long long Q=in(),op,L,R,x;
for ( int i=1;i<=Q;i++)
{
op=in();
if(op==1)
{
L=in(),R=in(),x=in();
update(L,x,Fenwick_Tree1);
update(R+1,-x,Fenwick_Tree1);
update(L,x*L,Fenwick_Tree2);
update(R+1,-x*(R+1),Fenwick_Tree2);
}
else
{
L=in(),R=in();
L=A[L-1]+L*query(L-1,Fenwick_Tree1)-query(L-1,Fenwick_Tree2);
R=A[R]+(R+1)*query(R,Fenwick_Tree1)-query(R,Fenwick_Tree2);
printf("%lld\n",R-L);
}
}
}
杂项
树状数组求逆序对
做法其实和I中的Star一题相似,先离散化,然后过程几乎相同,但是需要修改计数的对象。这里不写了。
树状数组求RMQ
TODO
树状数组的作用,局限和推广
作用
以常数较低,空间浪费较少的方式,处理区间问题。
局限
必须在区间某运算与其逆运算均成立的情况下使用。
推广:ST表
ST表思想与树状数组相同。但它比树状数组存储的信息多一点,如果我们将树状数组前后倒置,即得到ST表的一部分。我们即不需要满足区间运算性质,而直接运用合并的方法来处理这个问题。容易证明,其空间复杂度 O(Nlog2N) ,单次查询的时间复杂度 O(log2N) ,但如果进行修改,其复杂度即可达到平方阶。用ST表可解决无修改的RMQ,区间求和等问题。常数相较线段树更小。
例题:SHTSC 200X CLIMB
解法
一道需要注意细节的水题,用线段树和树状数组可能会被卡常。
代码
#include <math.h>
#include <stdio.h>
#include <string.h>
using namespace std;
short RMQ[5000001][30];
int N,Q,l,r;
short max(short a,short b)
{
return a>b?a:b;
}
short query( int l , int r )
{
short x=(short)(log(r-l+1)/log(2));
return max(RMQ[l][x],RMQ[r+1-(1<<x)][x]);
}
int main()
{
freopen("climb.in","r",stdin);
freopen("climb.out","w",stdout);
memset(RMQ,0,sizeof(RMQ));
scanf("%d",&N);
for ( int i=1;i<=N+1;i++)
scanf("%d",&RMQ[i][0]);
for ( int j=1;j<=log(N+1)/log(2);j++)
for ( int i=1;i+(1<<j)-1<=N+1;i++)
RMQ[i][j]=max(RMQ[i][j-1],RMQ[i+(1<<(j-1))][j-1]);
scanf("%d",&Q);
for ( int i=1;i<=Q;i++)
{
scanf("%d%d",&l,&r);
printf("%d\n",query(l+1,r+1));
}
return 0;
}