这两天笔者学习了一下树状数组,感觉真的是非常神奇的数据结构。(ORZ发明者)
如果我们要求带修改的序列区间和,可以直接用数组或者用前缀和做,但是数组求和是O(N),前缀和修改是O(N),两者的劣势也非常明显了,这时——树状数组出现了,相当于是一个分段前缀和,这可以形象地比喻为连接数组和前缀和的桥梁:树状数组修改和求和操作都是O(logN)级别的,而且代码短,写法比较固定,所以很少在树状数组中出现错误。
lowbit函数可以说是整个树状数组的灵魂,lowbit(x)=x&(-x),x-lowbit(x)就可以得到x的上一个管理区间,x+lowbit(x)就可以得到下一个管理区间,很神奇
[POJ 2352] Stars
因为坐标是按照纵坐标递增的顺序给定的,所以对于每个星星,只要求出之前有多少点的横坐标小于等于他。每次添加一个星星,将x~maxn的和都加1,表示以后的横坐标大于等于x的星星的等级都能加1.
#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;
#define lowbit(x) ((x)&(-x))
const int maxn=32005;
int n,level[15005],t[maxn],x,y;
int sum(int x){
int ans=0;
while (x){
ans+=t[x];
x-=lowbit(x);
}
return ans;
}
void add(int x){
while (x<maxn){
t[x]++;
x+=lowbit(x);
}
}
int main(){
scanf("%d",&n);
for (int i=1;i<=n;i++){
scanf("%d%d",&x,&y);
++x; //数据坐标从0开始 而树状数组不能处理0的情况
level[sum(x)]++;
add(x);
}
for (int i=0;i<n;i++) printf("%d\n",level[i]);
return 0;
}
一开始给定的图并不适合树状数组,所以我们要重新编号:每个点有left,right两个从root开始遍历,每访问一个点i就把时间戳time加1,然后i.left=time,遍历完所有子节点以后,i.right=time,表示i的子树里的最后一个节点编号,那么节点i管理的区间就是[i.left,i.right],剩下的就是模板了-_-||
#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;
#define lowbit(x) ((x)&(-x))
const int maxn=200005;
int node[maxn],next[maxn],head[maxn];
int n,m,f[maxn],s[maxn],left[maxn],right[maxn],tot;
void add(int x,int y) {node[++tot]=y;next[tot]=head[x];head[x]=tot;}
int time=0; //时间戳
void dfs(int t){
left[t]=++time;
for (int i=head[t];i;i=next[i])
dfs(node[i]);
right[t]=time;
}
void init(){
int x,y;
scanf("%d",&n);
memset(head,0,sizeof head);
for (int i=1;i<n;i++){
scanf("%d %d",&x,&y);
add(x,y);//add(y,x);
}
dfs(1);
for (int i=1;i<=n;i++)
s[i]=lowbit(i),f[i]=1;
}
int sum(int x){
int ans=0;
for (;x;x-=lowbit(x)) ans+=s[x];
return ans;
}
void edit(int x,int k){
for (;x<=n;x+=lowbit(x)) s[x]+=k;
}
int main(){
init();
int x;char c;
scanf("%d\n",&m);
while (m--){
scanf("%c %d\n",&c,&x);
if (c=='Q')
printf("%d\n",sum(right[x])-sum(left[x]-1));
else{
f[x]=-f[x];
edit(left[x],f[x]);
}
}
return 0;
}[BZOJ 1452] Count
虽然听上去是狠高大上的二维树状数组,不过也就是多了个For循环而已。。。每个颜色都用一个树状数组来表示,直接水过:D
#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;
#define lowbit(x) ((x)&(-x))
const int maxn=305;
int n,m,q,t[maxn][maxn][105],a[maxn][maxn],ans;
int x1,x2,y1,y2,c;
int get(){
int x=0;char p=getchar();
while (p<'0' || p>'9') p=getchar();
while (p>='0' && p<='9') x=x*10+p-'0',p=getchar();
return x;
}
void edit(int x,int y,int c,int k){
for (int i=x;i<=n;i+=lowbit(i))
for (int j=y;j<=m;j+=lowbit(j))
t[i][j][c]+=k;
}
int sum(int x,int y,int c){
int ans=0;
for (int i=x;i;i-=lowbit(i))
for (int j=y;j;j-=lowbit(j))
ans+=t[i][j][c];
return ans;
}
int main(){
scanf("%d %d\n",&n,&m);
memset(t,0,sizeof t);
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++){
scanf("%d",&a[i][j]);
edit(i,j,a[i][j],1);
}
scanf("%d\n",&q);char x;
while (q--){
if (getchar()=='1'){
scanf("%d %d",&x1,&y1);
edit(x1,y1,a[x1][y1],-1);
scanf("%d\n",&a[x1][y1]);
edit(x1,y1,a[x1][y1],1);
}
else{
scanf("%d %d %d %d %d\n",&x1,&x2,&y1,&y2,&c);
ans=sum(x2,y2,c)-sum(x2,y1-1,c)-sum(x1-1,y2,c)+sum(x1-1,y1-1,c);
printf("%d\n",ans);
}
}
return 0;
}[HDU 4031] Attack
一个点的防御失败次数=总防御次数-防御成功次数。
总防御次数用树状数组,区间修改单点查询。数组s存储原来的值(在这题里面可以去掉),数组flag为标记,表示修改量,如果要把区间[a,b]的值加1 ,那么就是[1,b]的标记+1,[1,a-1]的标记-1。
防御成功次数用暴力+优化,记录某个点上次已经访问到哪里和该点已经成功防御次数,注意一开始一次都没防御的时候要特殊处理。
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
#define lowbit(x) ((x)&(-x))
const int maxn=200005;
int n,m,T,k,tot,flag[maxn];
int time[maxn],def[maxn],att[maxn][2];
void init(){
memset(att,0,maxn);tot=0;
memset(flag,0,maxn);
memset(def,0,maxn);
memset(time,0,maxn);
scanf("%d %d %d\n",&n,&m,&k);
}
void edit(int x,int k){
for (;x;x-=lowbit(x)) flag[x]+=k;
}
int sum(int x){
int ans=0;
for (;x<=n;x+=lowbit(x)) ans+=flag[x];
return ans;
}
void update(int x){
if (!time[x]) //一次都没防御的情况要特判
for (int i=1;i<=tot;i++)
if (att[i][0]<=x && att[i][1]>=x){
def[x]++;
time[x]=i;
break;
}
int p=0;
for (int i=time[x]+k;i<=tot;i+=k) //直接跳到下一次可以查找的位置
for (;i<=tot;i++)
if (att[i][0]<=x && att[i][1]>=x){
def[x]++;p=i;break;
}
if (p) time[x]=p;
}
int a,b;char s[20];
void work(){
scanf("%s",s);
if (s[0]=='A'){
scanf("%d %d\n",&a,&b);
att[++tot][0]=a;att[tot][1]=b;
edit(a-1,-1);edit(b,1);
}
else{
scanf("%d\n",&a);
int ans=sum(a);
update(a);
ans-=def[a];
printf("%d\n",ans);
}
}
int main(){
scanf("%d",&T);
for (int i=1;i<=T;i++){
init();
printf("Case %d:\n",i);
while (m--)
work();
}
return 0;
}
那树状数组的区间修改区间查询呢?像笔者这种蒟蒻还是老老实实滚回去写线段树吧。。。

本文介绍了树状数组这一高效数据结构,通过几个实例展示了其在区间和、区间修改等操作中的应用,并提供了完整的代码实现。

2764

被折叠的 条评论
为什么被折叠?



