树状数组 二维树状数组 1:单点修改,区间查询 #10117. 「一本通 4.1 练习 2」简单题
经过了一段时间的学习,总算是初步了解了树状数组的概念和应用,这篇随笔就要总结一下树状数组这个数据结构。
首先我们要弄明白树状数组是用来干什么的。直白的说,树状数组就是用来维护数列的前缀和的,并且时间复杂度低,效率高。
虽然树状数组不如线段树用途广,但它毕竟好写,而且常数小,所以说学会树状数组也是很有必要的。
首先是树状数组的几个基本操作:
1、求lowbit:
1 int low(int x) 2 { 3 return x&(-x); 4 }
2、对某个元素进行加法操作:
1 void add(int x,int y) 2 { 3 for(int i=x;i<=n;i+=low(i)) 4 { 5 c[i]+=y; 6 } 7 }
3、查询前缀和:
1 int ask(int x) 2 { 3 int ans=0; 4 for(int i=x;i>0;i-=low(i)) 5 { 6 ans+=c[i]; 7 } 8 return ans; 9 }
4、统计a[x]~~~a[y]的和:
1 sum=ask(y)-ask(x-1);
注意:树状数组的下标绝对不可以是0,因为lowbit(0)=0;会死循环。
补充:
我们已经学会的单点修改,那么如果让你进行区间修改,又该怎么办呢?
差分。
我们可以用树状数组来维护数列的差分,这样如果进行区间修改就只需要修改区间的收尾两端即可。
1 add(bb,k); 2 add(cc+1,-k);
上几道板子题:
基本的单点修改,区间查询:
1 #include<iostream> 2 #include<cstring> 3 #include<cmath> 4 #include<string> 5 #include<cstdio> 6 using namespace std; 7 int n,m; 8 int aa,bb,cc; 9 long long c[100000000]; 10 int low(int x) 11 { 12 return x&(-x); 13 } 14 void add(int x,int y) 15 { 16 while(x<=n) 17 { 18 c[x]+=y; 19 x+=low(x); 20 } 21 } 22 long long ask(int x) 23 { 24 long long ans=0; 25 while(x>0) 26 { 27 ans+=c[x]; 28 x-=low(x); 29 } 30 return ans; 31 } 32 int main() 33 { 34 scanf("%d%d",&n,&m); 35 for(int i=1;i<=n;i++) 36 { 37 scanf("%d",&aa); 38 add(i,aa); 39 } 40 for(int i=1;i<=m;i++) 41 { 42 scanf("%d%d%d",&aa,&bb,&cc); 43 if(aa==1) 44 { 45 add(bb,cc); 46 } 47 else 48 { 49 printf("%lld ",ask(cc)-ask(bb-1)); 50 } 51 } 52 return 0; 53 }
基本的区间修改,单点查询:
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<string> 5 #include<cstring> 6 using namespace std; 7 int n,m; 8 int now,last; 9 long long c[1000000]; 10 int aa,bb,cc,k; 11 int low(int x) 12 { 13 return x&(-x); 14 } 15 void add(int x,int y) 16 { 17 while(x<=n) 18 { 19 c[x]+=y; 20 x+=low(x); 21 } 22 } 23 long long ask(int x) 24 { 25 long long ans=0; 26 while(x>0) 27 { 28 ans+=c[x]; 29 x-=low(x); 30 } 31 return ans; 32 } 33 int main() 34 { 35 scanf("%d%d",&n,&m); 36 for(int i=1;i<=n;i++) 37 { 38 scanf("%d",&now); 39 add(i,now-last); 40 last=now; 41 } 42 for(int i=1;i<=m;i++) 43 { 44 scanf("%d",&aa); 45 if(aa==1) 46 { 47 scanf("%d%d%d",&bb,&cc,&k); 48 add(bb,k); 49 add(cc+1,-k); 50 } 51 else 52 { 53 scanf("%d",&bb); 54 printf("%lld ",ask(bb)); 55 } 56 } 57 return 0; 58 }
那么如果是区间修改,区间查询呢?
这里如果要证明比较麻烦,但是我们记住结论就好了呀
区间修改区间查询:
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<string> 5 #include<cmath> 6 using namespace std; 7 int n,m,k; 8 long long now,last; 9 long long aa,bb,cc; 10 long long s1,s2; 11 long long c1[10000000]; 12 long long c2[10000000]; 13 int low(int x) 14 { 15 return x&(-x); 16 } 17 void add1(long long x,long long y) 18 { 19 for(int i=x;i<=n;i+=low(i)) 20 { 21 c1[i]+=y; 22 } 23 } 24 void add2(long long x,long long y) 25 { 26 for(int i=x;i<=n;i+=low(i)) 27 { 28 c2[i]+=y; 29 } 30 } 31 long long ask1(long long x) 32 { 33 long long ans=0; 34 for(int i=x;i>0;i-=low(i)) 35 { 36 ans+=c1[i]; 37 } 38 return ans; 39 } 40 long long ask2(long long x) 41 { 42 long long ans=0; 43 for(int i=x;i>0;i-=low(i)) 44 { 45 ans+=c2[i]; 46 } 47 return ans; 48 } 49 int main() 50 { 51 scanf("%d%d",&n,&m); 52 for(long long i=1;i<=n;i++) 53 { 54 scanf("%lld",&now); 55 add1(i,now-last); 56 add2(i,(now-last)*(i-1)); 57 last=now; 58 } 59 for(int i=1;i<=m;i++) 60 { 61 scanf("%d",&k); 62 { 63 if(k==1) 64 { 65 scanf("%lld%lld%lld",&aa,&bb,&cc); 66 add1(aa,cc); 67 add1(bb+1,-cc); 68 add2(aa,(aa-1)*cc); 69 add2(bb+1,-bb*cc); 70 } 71 else 72 { 73 scanf("%lld%lld",&aa,&bb); 74 s1=bb*ask1(bb)-ask2(bb); 75 s2=(aa-1)*ask1(aa-1)-ask2(aa-1); 76 printf("%lld ",(s1-s2)); 77 } 78 } 79 } 80 return 0; 81 }
拓展一下,二维树状数组:
其实也不难,就是多了一层循环而已。
代码如下:
1 #include<cstring> 2 #include<cmath> 3 #include<string> 4 #include<cstdio> 5 #include<cstring> 6 using namespace std; 7 int n,m; 8 int k; 9 int aa,bb,cc,dd; 10 long long c[5000][5000]; 11 int low(int x) 12 { 13 return x&(-x); 14 } 15 void add(int x,int y,int z) 16 { 17 for(int i=x;i<=n;i+=low(i)) 18 for(int j=y;j<=m;j+=low(j)) 19 { 20 c[i][j]+=z; 21 } 22 } 23 long long ask(int x,int y) 24 { 25 long long ans=0; 26 for(int i=x;i>0;i-=low(i)) 27 { 28 for(int j=y;j>0;j-=low(j)) 29 { 30 ans+=c[i][j]; 31 } 32 } 33 return ans; 34 } 35 int main() 36 { 37 scanf("%d%d",&n,&m); 38 while(~scanf("%d",&k)) 39 { 40 if(k==1) 41 { 42 scanf("%d%d%d",&aa,&bb,&cc); 43 add(aa,bb,cc); 44 } 45 else 46 { 47 scanf("%d%d%d%d",&aa,&bb,&cc,&dd); 48 printf("%lld ",ask(cc,dd)-ask(cc,bb-1)-ask(aa-1,dd)+ask(aa-1,bb-1)); 49 } 50 } 51 return 0; 52 }
树状数组的用法十分固定,下面分析几道例题:
首先分析题目,由于给定的星星坐标是有序的,所以我们可以直接进行树状数组操作,我们以x为下标进行区间操作,再统计一下前缀和就OK了。
1 #include<iostream> 2 #include<cstring> 3 #include<cmath> 4 #include<string> 5 #include<cstdio> 6 using namespace std; 7 int n; 8 int a; 9 int b; 10 int c[1000000]; 11 int d[1000000]; 12 int low(int x) 13 { 14 return x&(-x); 15 } 16 void add(int x,int y) 17 { 18 while(x<=32001) 19 { 20 c[x]+=y; 21 x+=low(x); 22 } 23 } 24 int ask(int x) 25 { 26 int ans=0; 27 while(x>0) 28 { 29 ans+=c[x]; 30 x-=low(x); 31 } 32 return ans; 33 } 34 int main() 35 { 36 scanf("%d",&n); 37 for(int i=1;i<=n;i++) 38 { 39 scanf("%d%d",&a,&b); 40 int t=ask(a+1); 41 add(a+1,1); 42 d[t]++; 43 } 44 for(int i=0;i<n;i++) 45 { 46 printf("%d ",d[i]); 47 } 48 return 0; 49 }
开两个树状数组,分别维护左端点和右端点,然后作差就好了。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<string> 5 #include<cmath> 6 using namespace std; 7 int n,m,k; 8 int aa,bb; 9 int c1[1000000]; 10 int c2[1000000]; 11 int low(int x) 12 { 13 return x&(-x); 14 } 15 void add1(int x,int y) 16 { 17 while(x<=n) 18 { 19 c1[x]+=y; 20 x+=low(x); 21 } 22 } 23 void add2(int x,int y) 24 { 25 while(x<=n) 26 { 27 c2[x]+=y; 28 x+=low(x); 29 } 30 } 31 int ask1(int x) 32 { 33 int ans=0; 34 while(x>0) 35 { 36 ans+=c1[x]; 37 x-=low(x); 38 } 39 return ans; 40 } 41 int ask2(int x) 42 { 43 int ans=0; 44 while(x>0) 45 { 46 ans+=c2[x]; 47 x-=low(x); 48 } 49 return ans; 50 } 51 int main() 52 { 53 scanf("%d%d",&n,&m); 54 for(int i=1;i<=m;i++) 55 { 56 scanf("%d%d%d",&k,&aa,&bb); 57 if(k==1) 58 { 59 add1(aa,1); 60 add2(bb,1); 61 } 62 else 63 { 64 printf("%d ",ask1(bb)-ask2(aa-1)); 65 } 66 } 67 return 0; 68 }
基本的区间操作,注意开long long。
1 #include<iostream> 2 #include<cstring> 3 #include<string> 4 #include<cmath> 5 #include<cstdio> 6 using namespace std; 7 int n,k; 8 char aa; 9 int bb,cc; 10 long long c[1000000]; 11 int low(int x) 12 { 13 return x&(-x); 14 } 15 void add(int x,int y) 16 { 17 while(x<=n) 18 { 19 c[x]+=y; 20 x+=low(x); 21 } 22 } 23 long long ask(int x) 24 { 25 long long ans=0; 26 while(x>0) 27 { 28 ans+=c[x]; 29 x-=low(x); 30 } 31 return ans; 32 } 33 int main() 34 { 35 scanf("%d%d",&n,&k); 36 for(int i=1;i<=k;i++) 37 { 38 scanf("%s",&aa); 39 if(aa=='A') 40 { 41 scanf("%d",&bb); 42 printf("%lld ",ask(bb)); 43 } 44 if(aa=='B') 45 { 46 scanf("%d%d",&bb,&cc); 47 add(bb,cc); 48 } 49 if(aa=='C') 50 { 51 scanf("%d%d",&bb,&cc); 52 add(bb,-cc); 53 } 54 } 55 return 0; 56 }
#10117. 「一本通 4.1 练习 2」简单题
比较有意思的一道题,区间修改,单点查询,但是问题是如何处理0和1,其实很简单,%2就好了啊。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<string> 5 #include<cmath> 6 using namespace std; 7 int n,m; 8 int aa,bb,cc; 9 int c[1000000]; 10 int low(int x) 11 { 12 return x&(-x); 13 } 14 void add(int x,int y) 15 { 16 while(x<=n) 17 { 18 c[x]+=y; 19 x+=low(x); 20 } 21 } 22 int ask(int x) 23 { 24 int ans=0; 25 while(x>0) 26 { 27 ans+=c[x]; 28 x-=low(x); 29 } 30 return ans; 31 } 32 int main() 33 { 34 scanf("%d%d",&n,&m); 35 for(int i=1;i<=m;i++) 36 { 37 scanf("%d",&aa); 38 if(aa==1) 39 { 40 scanf("%d%d",&bb,&cc); 41 add(bb,1); 42 add(cc+1,-1); 43 } 44 else 45 { 46 scanf("%d",&bb); 47 printf("%d ",ask(bb)%2); 48 } 49 } 50 return 0; 51 }
洛谷 P4939 Agent2
还是十分简单的区间修改,单点查询,熟练掌握就好。
1 #include<iostream> 2 #include<cmath> 3 #include<string> 4 #include<cmath> 5 #include<cstdio> 6 #include<cstring> 7 using namespace std; 8 int n,m; 9 int aa,bb,cc; 10 long long c[10000000]; 11 int low(int x) 12 { 13 return x&(-x); 14 } 15 void add(int x,int y) 16 { 17 for(int i=x;i<=n;i+=low(i)) 18 { 19 c[i]+=y; 20 } 21 } 22 long long ask(int x) 23 { 24 long long ans=0; 25 for(int i=x;i>0;i-=low(i)) 26 { 27 ans+=c[i]; 28 } 29 return ans; 30 } 31 int main() 32 { 33 scanf("%d%d",&n,&m); 34 for(int i=1;i<=m;i++) 35 { 36 scanf("%d",&aa); 37 if(aa==0) 38 { 39 scanf("%d%d",&bb,&cc); 40 add(bb,1); 41 add(cc+1,-1); 42 } 43 else 44 { 45 scanf("%d",&bb); 46 printf("%lld ",ask(bb)); 47 } 48 } 49 return 0; 50 }
二维树状数组的应用,记得%2。
1 #include<iostream> 2 #include<string> 3 #include<iostream> 4 #include<cstdio> 5 #include<cstring> 6 #include<cmath> 7 using namespace std; 8 int c[2000][2000]; 9 int n,m,t; 10 int aa,bb,cc,dd; 11 char k; 12 int low(int x) 13 { 14 return x&(-x); 15 } 16 void add(int x,int y,int z) 17 { 18 for(int i=x;i<=n;i+=low(i)) 19 for(int j=y;j<=n;j+=low(j)) 20 { 21 c[i][j]+=z; 22 } 23 } 24 int ask(int x,int y) 25 { 26 int ans=0; 27 for(int i=x;i>0;i-=low(i)) 28 for(int j=y;j>0;j-=low(j)) 29 { 30 ans+=c[i][j]; 31 } 32 return ans; 33 } 34 int main() 35 { 36 scanf("%d",&t); 37 for(int i=1;i<=t;i++) 38 { 39 scanf("%d%d",&n,&m); 40 memset(c,0,sizeof(c)); 41 for(int i=1;i<=m;i++) 42 { 43 scanf("%s",&k); 44 if(k=='C') 45 { 46 scanf("%d%d%d%d",&aa,&bb,&cc,&dd); 47 add(aa,bb,1); 48 add(aa,dd+1,1); 49 add(cc+1,bb,1); 50 add(cc+1,dd+1,1); 51 } 52 else 53 { 54 scanf("%d%d",&aa,&bb); 55 printf("%d ",ask(aa,bb)%2); 56 } 57 } 58 printf(" "); 59 } 60 return 0; 61 62 }
洛谷 P4054 [JSOI2009]计数问题
我们可以对每一个权值开一个二维树状数组,如果修改就先将原来的权值减去1,在将修改后的权值加上1。
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<cstring> 5 #include<string> 6 using namespace std; 7 int n,m,q,k; 8 int ju[305][305]; 9 int c[305][305][101]; 10 int aa,bb,cc,dd,ee; 11 int low(int x) 12 { 13 return x&(-x); 14 } 15 void add(int x,int y,int z,int t) 16 { 17 for(int i=x;i<=n;i+=low(i)) 18 { 19 for(int j=y;j<=m;j+=low(j)) 20 { 21 c[i][j][z]+=t; 22 } 23 } 24 } 25 int ask(int x,int y,int z) 26 { 27 int ans=0; 28 for(int i=x;i>0;i-=low(i)) 29 { 30 for(int j=y;j>0;j-=low(j)) 31 { 32 ans+=c[i][j][z]; 33 } 34 } 35 return ans; 36 } 37 int main() 38 { 39 scanf("%d%d",&n,&m); 40 for(int i=1;i<=n;i++) 41 for(int j=1;j<=m;j++) 42 { 43 scanf("%d",&ju[i][j]); 44 add(i,j,ju[i][j],1); 45 } 46 scanf("%d",&q); 47 for(int i=1;i<=q;i++) 48 { 49 scanf("%d",&k); 50 if(k==1) 51 { 52 scanf("%d%d%d",&aa,&bb,&cc); 53 add(aa,bb,ju[aa][bb],-1); 54 ju[aa][bb]=cc; 55 add(aa,bb,ju[aa][bb],1); 56 } 57 if(k==2) 58 { 59 scanf("%d%d%d%d%d",&aa,&bb,&cc,&dd,&ee); 60 printf("%d ",ask(bb,dd,ee)-ask(bb,cc-1,ee)-ask(aa-1,dd,ee)+ask(aa-1,cc-1,ee)); 61 } 62 } 63 return 0; 64 }
其实树状数组还有一个强大的功能,那就是求逆序对,然而有时数的大小太大,导致不可以作为数组的下标,怎么办呢。这时就要用到离散化了。什么是离散化呢?就是不改变相对大小的前提下,将分散的数据集中起来,比如说1,10,100,1000,10000这5个数,我们可以将其离散化为1,2,3,4,5,这样就可以作为数组的下标进行统计了。
那么如何离散化呢?
首先我们开一个结构体,记录每个元素的大小和在数列中的位置,然后我们按照元素的大小进行排序,这样在统计一下就得到了离散化的数据了。
大家可以结合这道例题具体体会:
P1908 逆序对
1 #include<iostream> 2 #include<cstdio> 3 #include<string> 4 #include<cmath> 5 #include<cstring> 6 #include<algorithm> 7 using namespace std; 8 struct edge 9 { 10 int v,num; 11 }g[1000000]; 12 long long c[1000000]; 13 int a[1000000]; 14 int n,aa; 15 long long tot; 16 int num=1; 17 bool cmp(edge a,edge b) 18 { 19 return a.v<b.v; 20 } 21 int low(int x) 22 { 23 return x&(-x); 24 } 25 void add(int x,int y) 26 { 27 for(int i=x;i<=n;i+=low(i)) 28 { 29 c[i]+=y; 30 } 31 } 32 long long ask(int x) 33 { 34 long long ans=0; 35 for(int i=x;i>0;i-=low(i)) 36 { 37 ans+=c[i]; 38 } 39 return ans; 40 } 41 int main() 42 { 43 scanf("%d",&n); 44 for(int i=1;i<=n;i++) 45 { 46 scanf("%d",&g[i].v); 47 g[i].num=i; 48 } 49 sort(g+1,g+1+n,cmp); 50 g[0].v=g[1].v; 51 for(int i=1;i<=n;i++) 52 { 53 if(g[i].v!=g[i-1].v) num++; 54 a[g[i].num]=num; 55 } 56 for(int i=n;i>=1;i--) 57 { 58 tot+=ask(a[i]-1); 59 add(a[i],1); 60 } 61 cout<<tot; 62 return 0; 63 }
还有几道相似的例题:
P3608 [USACO17JAN]Balanced Photo平衡的照片
P1637 三元上升子序列
都要用到离散化和求逆序对。
具体分析一下:p3608 这道题说白了就是正反各求一次逆序对,然后枚举出所有的正反逆序对个数严格超过2倍个元素个数。
1 #include<iostream> 2 #include<string> 3 #include<cstdio> 4 #include<cstring> 5 #include<cmath> 6 #include<algorithm> 7 using namespace std; 8 struct nod 9 { 10 int v,num; 11 }g[1000000]; 12 int n,num; 13 int a[1000000]; 14 int c1[1000000]; 15 int c2[1000000]; 16 long long b1[1000000]; 17 long long b2[1000000]; 18 int tot; 19 int low(int x) 20 { 21 return x&(-x); 22 } 23 void add1(int x,int y) 24 { 25 for(int i=x;i<=n;i+=low(i)) 26 { 27 c1[i]+=y; 28 } 29 } 30 void add2(int x,int y) 31 { 32 for(int i=x;i<=n;i+=low(i)) 33 { 34 c2[i]+=y; 35 } 36 } 37 long long ask1(int x) 38 { 39 long long ans=0; 40 for(int i=x;i>0;i-=low(i)) 41 { 42 ans+=c1[i]; 43 } 44 return ans; 45 } 46 long long ask2(int x) 47 { 48 long long ans=0; 49 for(int i=x;i>0;i-=low(i)) 50 { 51 ans+=c2[i]; 52 } 53 return ans; 54 } 55 bool cmp(nod a,nod b) 56 { 57 return a.v<b.v; 58 } 59 int main() 60 { 61 scanf("%d",&n); 62 for(int i=1;i<=n;i++) 63 { 64 scanf("%d",&g[i].v); 65 g[i].num=i; 66 } 67 sort(g+1,g+1+n,cmp); 68 for(int i=1;i<=n;i++) 69 a[g[i].num]=i; 70 for(int i=1;i<=n;i++) 71 { 72 add1(a[i],1); 73 b1[i]=ask1(n)-ask1(a[i]); 74 } 75 for(int i=n;i>=1;i--) 76 { 77 add2(a[i],1); 78 b2[i]=ask2(n)-ask2(a[i]); 79 } 80 for(int i=1;i<=n;i++) 81 { 82 if(b1[i]>2*b2[i]||b2[i]>2*b1[i]) 83 { 84 tot++; 85 } 86 87 } 88 printf("%d",tot); 89 return 0; 90 }
p1637 这道题可以转换一下思路,先求出以一个数为结尾的顺序对的个数,再求出以这个数为开头的顺序对(可以转换为倒序求逆序对)的个数,然后根据乘法原理相乘就好了。
注意:这道题的数据可能会有重复,所以一定要判重。
1 #include<iostream> 2 #include<cmath> 3 #include<cstring> 4 #include<string> 5 #include<cstdio> 6 #include<algorithm> 7 using namespace std; 8 struct nod 9 { 10 int v,num; 11 }g[1000000]; 12 bool cmp(nod a,nod b) 13 { 14 return a.v<b.v; 15 } 16 int n,num=1; 17 int a[1000000]; 18 long long b1[1000000]; 19 long long b2[1000000]; 20 int c1[1000000]; 21 int c2[1000000]; 22 long long ans; 23 int low(int x) 24 { 25 return x&(-x); 26 } 27 void add1(int x,int y) 28 { 29 for(int i=x;i<=n;i+=low(i)) 30 { 31 c1[i]+=y; 32 } 33 } 34 void add2(int x,int y) 35 { 36 for(int i=x;i<=n;i+=low(i)) 37 { 38 c2[i]+=y; 39 } 40 } 41 long long ask1(int x) 42 { 43 long long ans=0; 44 for(int i=x;i>0;i-=low(i)) 45 { 46 ans+=c1[i]; 47 } 48 return ans; 49 } 50 long long ask2(int x) 51 { 52 long long ans=0; 53 for(int i=x;i>0;i-=low(i)) 54 { 55 ans+=c2[i]; 56 } 57 return ans; 58 } 59 int main() 60 { 61 scanf("%d",&n); 62 for(int i=1;i<=n;i++) 63 { 64 scanf("%d",&g[i].v); 65 g[i].num=i; 66 } 67 sort(g+1,g+n+1,cmp); 68 g[0].v=g[1].v; 69 for(int i=1;i<=n;i++) 70 { 71 if(g[i].v!=g[i-1].v) num++; 72 a[g[i].num]=num; 73 } 74 for(int i=1;i<=n;i++) 75 { 76 add1(a[i],1); 77 b1[i]=ask1(a[i]-1); 78 } 79 for(int i=n;i>=1;i--) 80 { 81 add2(a[i],1); 82 b2[i]=ask2(n)-ask2(a[i]); 83 } 84 for(int i=1;i<=n;i++) 85 { 86 ans+=b1[i]*b2[i]; 87 } 88 printf("%lld",ans); 89 return 0; 90 }
这里还有一道很有意思的题。
我们可以把区间看成横纵坐标,这样题目就变成了数星星 Stars,只不过要求出在这个点左上方的点的个数,这里一定要判重,以及排序的先后顺序,™wa了好多次
注意离散化多关键词排序和判重,其实还是很容易的,注意好细节。
1 #include<iostream> 2 #include<cstdio> 3 #include<string> 4 #include<cmath> 5 #include<algorithm> 6 #include<cstring> 7 using namespace std; 8 struct edge 9 { 10 int x,y,num; 11 }g[1000000]; 12 int c[1000000]; 13 int d[1000000]; 14 int n; 15 int cmp1(edge a,edge b) 16 { 17 if(a.y==b.y) 18 return a.x<b.x; 19 else 20 return a.y>b.y; 21 } 22 int low(int x) 23 { 24 return x&(-x); 25 } 26 void add(int x,int y) 27 { 28 for(int i=x;i<=100001;i+=low(i)) 29 { 30 c[i]+=y; 31 } 32 } 33 int ask(int x) 34 { 35 int ans=0; 36 for(int i=x;i>0;i-=low(i)) 37 { 38 ans+=c[i]; 39 } 40 return ans; 41 } 42 int main() 43 { 44 while(scanf("%d",&n)) 45 { 46 if(n==0) return 0; 47 memset(g,0,sizeof(g)); 48 memset(c,0,sizeof(c)); 49 memset(d,0,sizeof(d)); 50 for(int i=1;i<=n;i++) 51 { 52 scanf("%d%d",&g[i].x,&g[i].y); 53 g[i].x++;g[i].y++; 54 g[i].num=i; 55 } 56 sort(g+1,g+n+1,cmp1); 57 for(int i=1;i<=n;i++) 58 { 59 int t=ask(g[i].x); 60 if(i!=1&&g[i].x==g[i-1].x&&g[i].y==g[i-1].y) 61 { 62 d[g[i].num]=d[g[i-1].num]; 63 } 64 else 65 { 66 d[g[i].num]=t; 67 } 68 add(g[i].x,1); 69 } 70 printf("%d",d[1]); 71 for(int i=2;i<=n;i++) 72 printf(" %d",d[i]); 73 printf(" "); 74 } 75 }
尽管树状数组的用途不如线段树广,但学会树状数组依然是很有必要的,总而言之,树状数组是一个非常有用的数据结构。