树状数组的应用
树状数组是一种较为高级的数据结构,下面总结一下具体应用(按照树神讲的顺序:
树状数组Binary Indexed Trees简称BIT。其平摊了修改和查询的时间复杂度,使他们都成为O(logN)的级别!
1.单点修改区间查询。(大家都会不再赘述。
2.区间修改单点查询,然后利用树状数组进行差分的求和,然后即可实现区间修改。
伪代码:add(x,1);add(y+1,-1);x,y为区间修改。
3.单点修改加求区间第k小的数字,很不好写呢,细节比较重要,wa了3次。poj 2985
#include<bits/stdc++.h> #include<iostream> #include<cmath> #include<ctime> #include<cstdio> #include<iomanip> #include<cstring> #include<string> #include<stack> #include<algorithm> #include<map> #include<queue> #include<deque> #include<vector> #include<set> using namespace std; inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } void put(int x) { if(x==0){putchar('0');putchar('\n');return;} if(x<0)putchar('-'),x=-x; int num=0;char ch[50]; while(x)ch[++num]=x%10+'0',x/=10; while(num)putchar(ch[num--]); putchar('\n');return; } const int maxn=200002; int a[maxn],c[maxn],f[maxn]; int n,m; int getfather(int x){return x==f[x]?x:f[x]=getfather(f[x]);} void add(int x,int y){for(;x;x-=x&(-x))c[x]+=y;return;} int ask(int x){int ans=0;for(;x<=n;x+=x&(-x))ans+=c[x];return ans;} int main() { //freopen("1.in","r",stdin); n=read();m=read(); for(int i=1;i<=n;i++)f[i]=i,a[i]=1,add(a[i],1); for(int i=1;i<=m;i++) { int p,x,y; p=read(); if(p==0) { x=read();y=read(); int xx=getfather(x); int yy=getfather(y); if(xx!=yy) { f[xx]=yy; add(a[xx],-1);//wa的原因 add(a[yy],-1);//让他们的父亲减 a[yy]+=a[xx];a[xx]=0; add(a[yy],1); } else continue; } if(p==1) { x=read(); int l=1,r=n; while(l+1<r) { int mid=(l+r)>>1; if(ask(mid)>=x)l=mid;//寻找那个刚好合适的人呢 else r=mid;//一直寻找 } if(ask(r)>=x)put(r);//r比l更优注意~! else put(l); } } return 0; }
当然还有一种二进制的方法直接得出不过需要转换一下,本人还不懂..
#include<bits/stdc++.h> #include<iostream> #include<cmath> #include<ctime> #include<cstdio> #include<iomanip> #include<cstring> #include<string> #include<stack> #include<algorithm> #include<map> #include<queue> #include<deque> #include<vector> #include<set> using namespace std; inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } void put(int x) { if(x==0){putchar('0');putchar('\n');return;} if(x<0)putchar('-'),x=-x; int num=0;char ch[50]; while(x)ch[++num]=x%10+'0',x/=10; while(num)putchar(ch[num--]); putchar('\n');return; } const int maxn=200002; int a[maxn],c[maxn],f[maxn]; int n,m,sum; int getfather(int x){return x==f[x]?x:f[x]=getfather(f[x]);} void add(int x,int y){for(;x<=n;x+=x&(-x))c[x]+=y;return;} int ask(int x){int ans=0;for(;x<=n;x+=x&(-x))ans+=c[x];return ans;} int main() { //freopen("1.in","r",stdin); n=read();m=read();sum=n; for(int i=1;i<=n;i++)f[i]=i,a[i]=1,add(a[i],1); for(int i=1;i<=m;i++) { int p,x,y; p=read(); if(p==0) { x=read();y=read(); int xx=getfather(x); int yy=getfather(y); if(xx!=yy) { f[xx]=yy; add(a[xx],-1);//wa的原因 add(a[yy],-1);//让他们的父亲减 a[yy]+=a[xx];a[xx]=0; add(a[yy],1); sum--;//还有几组 } else continue; } if(p==1) { x=read(); int ans=0,cnt=0;int u=sum-x+1;//卡好范围第u小 for(int i=20;i>=0;i--) { ans+=(1<<i); if(ans>n||cnt+c[ans]>=u)ans-=(1<<i); else cnt+=c[ans]; } put(ans+1); } } return 0; }
4.可以进行二维的树状数组,也就是一维变二维;
二维的树状数组罢了其实很简单。vijos 1512;
#include<iomanip> #include<iostream> #include<cmath> #include<cstdio> #include<cstring> #include<string> #include<map> #include<algorithm> #include<ctime> #include<vector> #include<queue> #include<deque> #include<set> #include<stack> using namespace std; inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } void put(int x) { if(x==0) { putchar('0'); putchar('\n'); return; } if(x<0) { putchar('-'); x=-x; } int num=0;char ch[25]; while(x)ch[++num]=x%10+'0',x/=10; while(num)putchar(ch[num--]); putchar('\n'); } const int maxn=1200; int n,p; int c[maxn][maxn]; void add(int x,int y,int z) { for(int i=x;i<=n;i+=i&(-i)) for(int j=y;j<=n;j+=j&(-j)) c[i][j]+=z; } int ask(int x,int y) { int ans=0; for(int i=x;i;i-=i&(-i)) for(int j=y;j;j-=j&(-j)) ans+=c[i][j]; return ans; } int main() { //freopen("1.in","r",stdin); n=read()+1; while(1) { p=read(); if(p==3)break; if(p==1) { int x,y,k; x=read()+1;y=read()+1;k=read(); add(x,y,k); } if(p==2) { int x,x1,s,s1; x=read()+1;x1=read()+1;s=read()+1;s1=read()+1; put(ask(s,s1)-ask(s,x1-1)-ask(x-1,s1)+ask(x-1,x1-1)); } } return 0; }
5.图腾计数树状数组应用 CH4201
数出左边比当前点高的和低和比右边点高或低的相乘累加即可。一遍a~!
#include<bits/stdc++.h> #include<iomanip> #include<iostream> #include<cmath> #include<cstdio> #include<cstring> #include<string> #include<map> #include<algorithm> #include<ctime> #include<vector> #include<queue> #include<deque> #include<set> #include<stack> using namespace std; inline long long read() { long long x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } void put(long long x) { if(x==0) { putchar('0'); putchar(' '); return; } if(x<0) { putchar('-'); x=-x; } long long num=0;char ch[60]; while(x)ch[++num]=x%10+'0',x/=10; while(num)putchar(ch[num--]); putchar(' '); } const long long maxn=200002; long long a[maxn],c[maxn],u[maxn],r[maxn],l[maxn]; long long w[maxn],y[maxn]; long long n,ans=0,num=0; void add(long long x,long long y){for(;x<=n;x+=x&(-x))c[x]+=y;return;} long long ask(long long x){long long ans=0;for(;x;x-=x&(-x))ans+=c[x];return ans;} void add1(long long x,long long y){for(;x;x-=x&(-x))u[x]+=y;return;} long long ask1(long long x){long long ans=0;for(;x<=n;x+=x&(-x))ans+=u[x];return ans;} int main() { //freopen("1.in","r",stdin); n=read(); for(long long i=1;i<=n;i++) { a[i]=read(); add(a[i],1); add1(a[i],1); l[i]=ask1(a[i]+1); w[i]=ask(a[i]-1); } memset(u,0,sizeof(u)); memset(c,0,sizeof(c)); for(long long i=n;i>=1;i--) { add(a[i],1); add1(a[i],1); r[i]=ask1(a[i]+1); y[i]=ask(a[i]-1); } for(long long i=1;i<=n;i++) { ans+=l[i]*r[i]; num+=w[i]*y[i]; } put(ans);put(num); return 0; }
关于逆序对的求解在另一篇博文之中,本文不再赘述。
6.二维数点
直接转换一下可以考虑排序如果区间无序的话,然后进行数点即可。
#include<bits/stdc++.h> #include<iostream> #include<cstdio> #include<cstring> #include<string> #include<cmath> #include<iomanip> #include<ctime> #include<queue> #include<map> #include<vector> #include<stack> #include<algorithm> using namespace std; inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } const int maxn=60002; int n,c[maxn],maxx; int x[maxn],y[maxn]; void add(int x,int y){for(;x<=maxx;x+=x&(-x))c[x]+=y;} int ask(int x){int ans=0;for(;x;x-=x&(-x))ans+=c[x];return ans;} int main() { //freopen("1.in","r",stdin); n=read(); for(int i=1;i<=n;i++) { x[i]=read();y[i]=read(); x[i]+=1;y[i]+=1; maxx=max(maxx,x[i]); } for(int i=1;i<=n;i++) { add(x[i],1); printf("%d\n",ask(x[i])-1); } return 0; }
8.一道比较有思维难度的题
很朴实的题,除了范围有点大似乎没什么不好这个也其实很简单,具体细节看代码。
#include<bits/stdc++.h> #include<iomanip> #include<iostream> #include<cmath> #include<cstdio> #include<cstring> #include<string> #include<map> #include<algorithm> #include<ctime> #include<vector> #include<queue> #include<deque> #include<set> #include<stack> using namespace std; inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } const int maxn=1000008; struct wy { int x,y; int an,id; }t[maxn]; int n,m; int a[maxn],pre[maxn];//prefix前缀。 int cmp(wy x,wy y){if(x.x==y.x)return x.y<y.y;return x.y<y.y;} int my(wy x,wy y){return x.id<y.id;} int c[maxn],ans=0; void add(int x,int y){for(;x<=n;x+=x&(-x))c[x]+=y;return;} int ask(int x){int ans=0;for(;x;x-=x&(-x))ans+=c[x];return ans;} void put(int x) { if(x==0) { putchar('0'); putchar('\n'); return; } if(x<0) { putchar('-'); x=-x; } int num=0;char ch[16]; while(x)ch[++num]=x%10+'0',x/=10; while(num)putchar(ch[num--]); putchar('\n'); } int main() { //freopen("1.in","r",stdin); n=read(); for(int i=1;i<=n;i++)a[i]=read()+1; m=read(); for(int i=1;i<=m;i++)t[i].x=read(),t[i].y=read(),t[i].id=i; sort(t+1,t+1+m,cmp); //for(int i=1;i<=m;i++)printf("%d %d\n",t[i].x,t[i].y); for(int i=1,j=1;i<=n;i++) { if(pre[a[i]]==0) { add(i,1); pre[a[i]]=i; } else { add(pre[a[i]],-1); add(i,1); pre[a[i]]=i; } while(i==t[j].y) { t[j].an=ask(t[j].y)-ask(t[j].x-1); j++; } } sort(t+1,t+1+m,my); for(int i=1;i<=m;i++)put(t[i].an); return 0; }
最后:
注意:
因为lowbit(0)=0所以树状数组的下标只能从1开始,如果题干中出现0的情况,把所有输入数据都+1是个不错的解决方法。
树状数组可以做到区间修改,区间查询,方法为两个树状数组维护原序列。
树状数组的求和操作适用于所有满足结合律的操作,例如异或。
谁道闲情抛掷久,每到春来,惆怅还依旧。