树状数组初探

来源:互联网 发布:方大炭素历史交易数据 编辑:程序博客网 时间:2024/06/05 01:20

这两天笔者学习了一下树状数组,感觉真的是非常神奇的数据结构。(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;}


[POJ 3321]  Apple Tree

一开始给定的图并不适合树状数组,所以我们要重新编号:每个点有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;}

那树状数组的区间修改区间查询呢?像笔者这种蒟蒻还是老老实实滚回去写线段树吧。。。

0 0