树状数组总结
2013-05-02 18:23
302 查看
最近做了近20道树状数组题目,发现有点痴迷于它了,这个数据结构真是太优美了,小巧又强悍,编程复杂度极其低,大爱呀!
先丢上模板
lowbit操作:
add操作:
Query操作:
运用树状数组我们可以解决一下几类问题:
一维:
POJ3928 - Ping pong
POJ - Ultra-QuickSort(求逆序数)
SGU180 - Inversions(求逆序数,需要离散)
POJ2352 – Stars(下标必须从1开始)
UVa12086 – Potentiometers
POJ24810 – Cows
POJ3067 – Japan
HDU1166 - 敌兵布阵
POJ3321 - Apple Tree
POJ1990 – MooFest
HDU3015 - Disharmony Trees
SPOJ1029 - Matrix Summation
二维:
add操作:
Query操作:
题目:
POJ1195 - Mobile phones
此题当模板还是非常好
1、”X Y A“对网格C[x][y]增加A
2、”L B R T“ 查询所有(L<=X<=R,B<=Y<=T)的网格C[X[Y],并返回它们的总和
二维:
每次给出矩阵的左下角坐标(x1,y1)和右上坐标(x2,y2),将矩阵中的所有数组取反,或者给出一个坐标(x,y),要求你查询A[x][y]的值是多少。
三维:
1、查询A[x][y][z]的奇偶性
2、对子立方体的每个元素的值进行增减
1、"C a b c" 对区间[a,b]内的所有数都加上c
2、"Q a b"询问区间[a,b]的和
【题目大意】
第一分钟,X说,要有矩阵,于是便有了一个里面写满了0的n×m矩阵。
第二分钟,L说,要能修改,于是便有了将左上角为(a,b),右下角为(c,d)的一个矩形区域内的全部数字加上一个值的操作。
第三分钟,k说,要能查询,于是便有了求给定矩形区域内的全部数字和的操作。
第四分钟,彩虹喵说,要基于二叉树的数据结构,于是便有了数据范围。
第五分钟,和雪说,要有耐心,于是便有了时间限制。
第六分钟,吃钢琴男说,要省点事,于是便有了保证运算过程中及最终结果均不超过32位有符号整数类型的表示范围的限制。
第七分钟,这道题终于造完了,然而,造题的神牛们再也不想写这道题的程序了。”
——《上帝造裸题的七分钟》
所以这个神圣的任务就交给你了。
【输入格式】
输入数据的第一行为X n m,代表矩阵大小为n×m。
从输入数据的第二行开始到文件尾的每一行会出现以下两种操作:
L a b c d delta —— 代表将(a,b),(c,d)为顶点的矩形区域内的所有数字加上delta。
k a b c d —— 代表求(a,b),(c,d)为顶点的矩形区域内所有数字的和。
请注意,k为小写。
【输出格式】
针对每个k操作,在单独的一行输出答案。
【样例输入】
X 4 4
L 1 1 3 3 2
L 2 2 4 4 1
k 2 2 3 3
【样例输出】
12
先看看sum和update变成什么样子了吧。
gs就是sum,gp就是update,由于需要多次调用,所以改了个短一点的名字。
单点加减,矩形求和并不难,直接用上面的两段就行了。
需要注意的是矩形的求和怎么求。上面的代码返回的是(1,1)-(x,y)矩形的和。
那么(x1,y1)-(x2,y2)的矩形和由下式给出:
sum(x2,y2)-sum(x1-1,y2)-sum(x2,y1-1)+sum(x1-1,y1-1)
画个图就很好理解了。
对于涉及矩形加减的情形,我们发现一维中的差分的办法在二维的情况用不出来,所以要改一下。
思考一下一维中的差分的另外一个含义:d[i]同时也表示d[i..n]的整体增量,d[i]+=k就意味着把d[i]..d
全部加上了k。
理解了之后就发现这个意义上可以推广到二维,假设原矩形初始全为0,以便接下来的叙述。
令a[x,y]表示(x,y)-(n,m)矩形的整体增量,其中(n,m)是边界。
那么(x1,y1)-(x2,y2)矩形整体加k的代码就是
gp(a,x1,y1,w); gp(a,x2+1,y1,-w);gp(a,x1,y2+1,-w); gp(a,x2+1,y2+1,w);
仍然是建议画个图来帮助理解。
至此,矩形加减,单点查询的问题得到了解决。
重头戏在这里,矩形加减,矩形求和。
求原矩形(1,1)-(x,y)的和,结果由下式给出
∑(i=1..x,j=1..y) a[i,j]*(x-i+1)*(y-j+1)
很好理解吧? 但是这个式子并不是那么容易求和的,展开一下求和的部分得到
a[i,j]* ( (x+1)(y+1) - (x+1)*j - (y+1)*x + i*j )
整个式子就是
(x+1)(y+1)∑(a[i,j]) - (x+1)∑(a[i,j]*j) - (y+1)∑(a[i,j]*i) + ∑(a[i,j]*i*j)
知道怎么处理了没,如果没有请回去理解一维的处理方法。
令b[i,j]=a[i,j]*i c[i,j]=a[i,j]*j d[i,j]=a[i,j]*i*j
维护a,b,c,d一共四个二维树状数组,圆满地完成了任务。
tyvj p1716就是实现这两个功能的裸题,下面给出完整代码。
先丢上模板
lowbit操作:
int lowbit(int x) { return x&-x; }
add操作:
void add(int x,int d) { while(x<=n) { c[x]+=d; x+=lowbit(x); } }
Query操作:
int sum(int x) { long ret=0; while(x>0) { ret+=c[x]; x-=lowbit(x); } return ret; }
运用树状数组我们可以解决一下几类问题:
一、单点修改,区间查询
这是树状数组最基本的操作一维:
POJ3928 - Ping pong
POJ - Ultra-QuickSort(求逆序数)
SGU180 - Inversions(求逆序数,需要离散)
POJ2352 – Stars(下标必须从1开始)
UVa12086 – Potentiometers
POJ24810 – Cows
POJ3067 – Japan
HDU1166 - 敌兵布阵
POJ3321 - Apple Tree
POJ1990 – MooFest
HDU3015 - Disharmony Trees
SPOJ1029 - Matrix Summation
二维:
add操作:
void add(int x,int y,int d) { int i,j; for(i=x;i<=n;i+=lowbit(i)) for(j=y;j<=n;j+=lowbit(j)) c[i][j]+=d; }
Query操作:
int sum(int x,int y) { int i,j,ret=0; for(i=x;i>0;i-=lowbit(i)) for(j=y;j>0;j-=lowbit(j)) ret+=c[i][j]; return ret; }
题目:
POJ1195 - Mobile phones
此题当模板还是非常好
题目大意
给定一个N*N的网格,刚开始每个网格的值都是0,接下来会对这些网格进行操作,有一下两种操作:1、”X Y A“对网格C[x][y]增加A
2、”L B R T“ 查询所有(L<=X<=R,B<=Y<=T)的网格C[X[Y],并返回它们的总和
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#define MAXN 1050
using namespace std;
int c[MAXN][MAXN];
int n;
int lowbit(int x) { return x&-x; }
int sum(int x,int y) { int i,j,ret=0; for(i=x;i>0;i-=lowbit(i)) for(j=y;j>0;j-=lowbit(j)) ret+=c[i][j]; return ret; }
void add(int x,int y,int d) { int i,j; for(i=x;i<=n;i+=lowbit(i)) for(j=y;j<=n;j+=lowbit(j)) c[i][j]+=d; }
int main(void)
{
int a,j,lb,x1,y1,x2,y2,ans;
while(cin>>a>>n)
{
memset(c,0,sizeof(c));
while(scanf("%d",&lb)==1&&lb!=3)
{
if(lb==2)
{
ans=0;
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
x1++; y1++;
x2++; y2++;
ans+=sum(x2,y2);
ans-=sum(x1-1,y2);
ans-=sum(x2,y1-1);
ans+=sum(x1-1,y1-1);
printf("%d\n",ans);
}
else
{
scanf("%d%d%d",&x1,&y1,&j);
x1++;
y1++;
add(x1,y1,j);
}
}
}
return 0;
}
二、单点查询,区间修改
一维:
HDU1556 - Color the ball
可以当模板题目大意
给定区间[1,N],初始时区间的每个数都是0,对其进行多次操作,每次给出两个数a和b,要求你对在区间[a,b]的数全部加1,在操作结束后输出每个数的值#include<iostream>
#include<cstring>
#include<cstdio>
#define MAXN 100005
using namespace std;
int c[MAXN];
int n;
int lowbit(int x) { return x&-x; }
void add(int x,int d) { while(x<=n) { c[x]+=d; x+=lowbit(x); } }
int sum(int x)
{
int ret=0;
while(x>0)
{
ret+=c[x];
x-=lowbit(x);
}
return ret;
}
int main(void)
{
int i,j,l,r;
while(scanf("%d",&n)==1&&n)
{
memset(c,0,sizeof(c));
for(i=1;i<=n;i++)
{
scanf("%d%d",&l,&r);
add(l,1);
add(r+1,-1);
}
for(i=1;i<n;i++)
printf("%d ",sum(i));
printf("%d\n",sum(i));
}
return 0;
}
二维:
POJ2155 – Matrix
题目大意
给定一个大小为N*N的矩阵,每个元素要么为0要么为1,刚开始时A[i][j]=0,(1<=i,j<=N)每次给出矩阵的左下角坐标(x1,y1)和右上坐标(x2,y2),将矩阵中的所有数组取反,或者给出一个坐标(x,y),要求你查询A[x][y]的值是多少。
#include<iostream>
#include<cstdio>
#include<cstring>
#define MAXN 1005
using namespace std;
int c[MAXN][MAXN];
int n;
int lowbit(int x) { return x&-x; }
int sum(int x,int y) { int i,j,ret=0; for(i=x;i>0;i-=lowbit(i)) for(j=y;j>0;j-=lowbit(j)) ret+=c[i][j]; return ret; }
void add(int x,int y,int d) { int i,j; for(i=x;i<=n;i+=lowbit(i)) for(j=y;j<=n;j+=lowbit(j)) c[i][j]+=d; }
int main(void)
{
char ch;
int m,T,x1,y1,x2,y2,ans;
cin>>T;
while(T--)
{
scanf("%d%d",&n,&m);
memset(c,0,sizeof(c));
while(m--)
{
getchar();
scanf("%c",&ch);
if(ch=='C')
{
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
add(x1,y1,1);
add(x2+1,y1,1);
add(x1,y2+1,1);
add(x2+1,y2+1,1);
}
else
{
scanf("%d%d",&x1,&y1);
ans=sum(x1,y1)&1;
printf("%d\n",ans);
}
}
printf("\n");
}
return 0;
}
三维:
HDU3584 – Cube
题目大意
给定一个体积为N*N*N立方体,每个单位小立方体A[x][y][z]里有一个值,初始值全部为0,我们可以对立方体进行一下两种操作:1、查询A[x][y][z]的奇偶性
2、对子立方体的每个元素的值进行增减
#include<iostream>
#include<cstdio>
#include<cstring>
#define MAXN 105
using namespace std;
int c[MAXN][MAXN][MAXN];
int n;
int lowbit(int x) { return x&-x; }
void add(int x,int y,int z)
{
int i,j,k;
for(i=x;i<MAXN;i+=lowbit(i))
for(j=y;j<MAXN;j+=lowbit(j))
for(k=z;k<MAXN;k+=lowbit(k))
c[i][j][k]++;
}
int sum(int x,int y,int z)
{
int i,j,k,ret=0;
for(i=x;i>0;i-=lowbit(i))
for(j=y;j>0;j-=lowbit(j))
for(k=z;k>0;k-=lowbit(k))
ret+=c[i][j][k];
return ret;
}
int main()
{
int x1,x2,y1,y2,z1,z2,i,m,t;
while(scanf("%d%d",&n,&m)!=EOF)
{
memset(c,0,sizeof(c));
for(i=1;i<=m;i++)
{
scanf("%d",&t);
if(t)
{
scanf("%d%d%d%d%d%d",&x1,&y1,&z1,&x2,&y2,&z2);
add(x1,y1,z1);
add(x2+1,y1,z1);
add(x1,y2+1,z1);
add(x1,y1,z2+1);
add(x2+1,y2+1,z1);
add(x2+1,y1,z2+1);
add(x1,y2+1,z2+1);
add(x2+1,y2+1,z2+1);
}
else
{
scanf("%d%d%d",&x1,&y1,&z1);
printf("%d\n",sum(x1,y1,z1)&1);
}
}
}
return 0;
}
三、区间修改,区间查询
一维:
POJ3468 - A Simple Problem with Integers
题目大意
给定N个整数,A1,A2,A3…An。可以对这些数进行一下两种操作:1、"C a b c" 对区间[a,b]内的所有数都加上c
2、"Q a b"询问区间[a,b]的和
#include<iostream>
#include<cstring>
#include<cstdio>
#define MAXN 100005
long long a[MAXN],c[MAXN],b[MAXN];
int n,m;
using namespace std;
int lowbit(int x) { return x&-x; }
void add(long long a[],int x,int d)
{
while(x<=n)
{
a[x]+=d;
x+=lowbit(x);
}
}
long long sum(long long a[],int x)
{
long long ret=0;
while(x>0)
{
ret+=a[x];
x-=lowbit(x);
}
return ret;
}
int main(void)
{
int i,l,r,d;
long long ans;
char ch;
while(scanf("%d%d",&n,&m)!=EOF)
{
memset(c,0,sizeof(c));
memset(b,0,sizeof(b));
a[0]=0;
for(i=1; i<=n; i++)
{
scanf("%lld",&a[i]);
a[i]+=a[i-1];
}
for(i=1; i<=m; i++)
{
getchar();
scanf("%c",&ch);
if(ch=='C')
{
scanf("%d%d%d",&l,&r,&d);
add(b,l,d);
add(b,r+1,-d);
add(c,l,d*l);
add(c,r+1,-(r+1)*d);
}
else
{
scanf("%d%d",&l,&r);
ans=a[r]-a[l-1]+(r+1)*sum(b,r)-l*(sum(b,l-1))-sum(c,r)+sum(c,l-1);
printf("%lld\n",ans);
}
}
}
return 0;
}
二维:
tyvj1716有裸题,不过现在上不去,所以没做,直接搬运wyl8899的总结好了,方便以后打印出来当模板【题目大意】
第一分钟,X说,要有矩阵,于是便有了一个里面写满了0的n×m矩阵。
第二分钟,L说,要能修改,于是便有了将左上角为(a,b),右下角为(c,d)的一个矩形区域内的全部数字加上一个值的操作。
第三分钟,k说,要能查询,于是便有了求给定矩形区域内的全部数字和的操作。
第四分钟,彩虹喵说,要基于二叉树的数据结构,于是便有了数据范围。
第五分钟,和雪说,要有耐心,于是便有了时间限制。
第六分钟,吃钢琴男说,要省点事,于是便有了保证运算过程中及最终结果均不超过32位有符号整数类型的表示范围的限制。
第七分钟,这道题终于造完了,然而,造题的神牛们再也不想写这道题的程序了。”
——《上帝造裸题的七分钟》
所以这个神圣的任务就交给你了。
【输入格式】
输入数据的第一行为X n m,代表矩阵大小为n×m。
从输入数据的第二行开始到文件尾的每一行会出现以下两种操作:
L a b c d delta —— 代表将(a,b),(c,d)为顶点的矩形区域内的所有数字加上delta。
k a b c d —— 代表求(a,b),(c,d)为顶点的矩形区域内所有数字的和。
请注意,k为小写。
【输出格式】
针对每个k操作,在单独的一行输出答案。
【样例输入】
X 4 4
L 1 1 3 3 2
L 2 2 4 4 1
k 2 2 3 3
【样例输出】
12
wyl8899大神的总结:
接下来到二维树状数组。先看看sum和update变成什么样子了吧。
inline int gs(int a[maxn][maxn],int x,int y){ int s=0,t; for(;x;x-=lowbit(x)) for(t=y;t;t-=lowbit(t)) s+=a[x][t]; return s; } inline void gp(int a[maxn][maxn],int x,int y,int w){ int t; for(;x<=n;x+=lowbit(x)) for(t=y;t<=m;t+=lowbit(t)) a[x][t]+=w; }
gs就是sum,gp就是update,由于需要多次调用,所以改了个短一点的名字。
单点加减,矩形求和并不难,直接用上面的两段就行了。
需要注意的是矩形的求和怎么求。上面的代码返回的是(1,1)-(x,y)矩形的和。
那么(x1,y1)-(x2,y2)的矩形和由下式给出:
sum(x2,y2)-sum(x1-1,y2)-sum(x2,y1-1)+sum(x1-1,y1-1)
画个图就很好理解了。
对于涉及矩形加减的情形,我们发现一维中的差分的办法在二维的情况用不出来,所以要改一下。
思考一下一维中的差分的另外一个含义:d[i]同时也表示d[i..n]的整体增量,d[i]+=k就意味着把d[i]..d
全部加上了k。
理解了之后就发现这个意义上可以推广到二维,假设原矩形初始全为0,以便接下来的叙述。
令a[x,y]表示(x,y)-(n,m)矩形的整体增量,其中(n,m)是边界。
那么(x1,y1)-(x2,y2)矩形整体加k的代码就是
gp(a,x1,y1,w); gp(a,x2+1,y1,-w);gp(a,x1,y2+1,-w); gp(a,x2+1,y2+1,w);
仍然是建议画个图来帮助理解。
至此,矩形加减,单点查询的问题得到了解决。
重头戏在这里,矩形加减,矩形求和。
求原矩形(1,1)-(x,y)的和,结果由下式给出
∑(i=1..x,j=1..y) a[i,j]*(x-i+1)*(y-j+1)
很好理解吧? 但是这个式子并不是那么容易求和的,展开一下求和的部分得到
a[i,j]* ( (x+1)(y+1) - (x+1)*j - (y+1)*x + i*j )
整个式子就是
(x+1)(y+1)∑(a[i,j]) - (x+1)∑(a[i,j]*j) - (y+1)∑(a[i,j]*i) + ∑(a[i,j]*i*j)
知道怎么处理了没,如果没有请回去理解一维的处理方法。
令b[i,j]=a[i,j]*i c[i,j]=a[i,j]*j d[i,j]=a[i,j]*i*j
维护a,b,c,d一共四个二维树状数组,圆满地完成了任务。
tyvj p1716就是实现这两个功能的裸题,下面给出完整代码。
#include<cstdio>
#include<cstring>
#define lowbit(x) ((x)&(-(x)))
const int maxn=2049;
int a[maxn][maxn],b[maxn][maxn],c[maxn][maxn],d[maxn][maxn];
int n,m;
inline int gs(int a[maxn][maxn],int x,int y){ int s=0,t; for(;x;x-=lowbit(x)) for(t=y;t;t-=lowbit(t)) s+=a[x][t]; return s; } inline void gp(int a[maxn][maxn],int x,int y,int w){ int t; for(;x<=n;x+=lowbit(x)) for(t=y;t<=m;t+=lowbit(t)) a[x][t]+=w; }
inline int sum(int x,int y){
return (x+1)*(y+1)*gs(a,x,y)-(y+1)*gs(b,x,y)-(x+1)*gs(c,x,y)+gs(d,x,y);
}
inline void update(int x1,int y1,int x2,int y2,int w){
gp(a,x1,y1,w); gp(a,x2+1,y1,-w);
gp(a,x1,y2+1,-w); gp(a,x2+1,y2+1,w);
gp(b,x1,y1,w*x1); gp(b,x2+1,y1,-w*(x2+1));
gp(b,x1,y2+1,-w*x1); gp(b,x2+1,y2+1,w*(x2+1));
gp(c,x1,y1,w*y1); gp(c,x2+1,y1,-w*y1);
gp(c,x1,y2+1,-w*(y2+1)); gp(c,x2+1,y2+1,w*(y2+1));
gp(d,x1,y1,w*x1*y1); gp(d,x2+1,y1,-w*(x2+1)*y1);
gp(d,x1,y2+1,-w*x1*(y2+1)); gp(d,x2+1,y2+1,w*(x2+1)*(y2+1));
}
int main(){
int x1,y1,x2,y2,w;
char ch;
scanf("%c",&ch);
while(ch!='X')scanf("%c",&ch);
scanf("%d%d\n",&n,&m);
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
memset(c,0,sizeof(c));
memset(d,0,sizeof(d));
while(scanf("%c",&ch)!=EOF){
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
if(ch=='L'){
scanf("%d\n",&w);
update(x1,y1,x2,y2,w);
}else{
scanf("\n");
printf("%d\n",sum(x2,y2)-sum(x1-1,y2)-sum(x2,y1-1)+sum(x1-1,y1-1));
}
}
return 0;
}