树状数组

       经过了一段时间的学习,总算是初步了解了树状数组的概念和应用,这篇随笔就要总结一下树状数组这个数据结构。

  首先我们要弄明白树状数组是用来干什么的。直白的说,树状数组就是用来维护数列的前缀和的,并且时间复杂度低,效率高。

  虽然树状数组不如线段树用途广,但它毕竟好写,而且常数小,所以说学会树状数组也是很有必要的。

  首先是树状数组的几个基本操作:

1、求lowbit:

 

1 int low(int x)
2 {
3     return x&(-x);
4 }
View Code

 

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 }
View Code

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 }
View Code

4、统计a[x]~~~a[y]的和:

1 sum=ask(y)-ask(x-1);
View Code

注意:树状数组的下标绝对不可以是0,因为lowbit(0)=0;会死循环。

补充:

我们已经学会的单点修改,那么如果让你进行区间修改,又该怎么办呢?

差分。

我们可以用树状数组来维护数列的差分,这样如果进行区间修改就只需要修改区间的收尾两端即可。

1 add(bb,k);
2 add(cc+1,-k);
View Code

 

上几道板子题:

P3374 【模板】树状数组 1

基本的单点修改,区间查询:

 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\n",ask(cc)-ask(bb-1));
50         }
51     }
52     return 0;
53 }
View Code

P3368 【模板】树状数组 2

基本的区间修改,单点查询:

 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\n",ask(bb));
55         }
56     }
57     return 0;
58 }
View Code

那么如果是区间修改,区间查询呢?

这里如果要证明比较麻烦,但是我们记住结论就好了呀

P3372 【模板】线段树 1

 区间修改区间查询:

 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\n",(s1-s2));
77             }
78         }
79     }
80     return 0;
81 }
View Code

拓展一下,二维树状数组:

 

二维树状数组 1:单点修改,区间查询

 

其实也不难,就是多了一层循环而已。

代码如下:

 

 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\n",ask(cc,dd)-ask(cc,bb-1)-ask(aa-1,dd)+ask(aa-1,bb-1));
49         }
50     }
51     return 0;
52 }
View Code

 


树状数组的用法十分固定,下面分析几道例题:

#10114 「一本通 4.1 例 2」数星星 Stars

 

首先分析题目,由于给定的星星坐标是有序的,所以我们可以直接进行树状数组操作,我们以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\n",d[i]);
47     }
48     return 0;
49 }
数星星

#10115 「一本通 4.1 例 3」校门外的树

开两个树状数组,分别维护左端点和右端点,然后作差就好了。

 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\n",ask1(bb)-ask2(aa-1));
65         }
66     }
67     return 0;
68 }
View Code

#10116 「一本通 4.1 练习 1」清点人数

基本的区间操作,注意开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\n",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 }
View Code

#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\n",ask(bb)%2);
48         }
49     }
50     return 0;
51 }
View Code

洛谷 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\n",ask(bb));
47         }
48     }
49     return 0;
50 }
View Code

poj 2155 Matrix

二维树状数组的应用,记得%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\n",ask(aa,bb)%2);
56             }
57         }
58         printf("\n");
59     }
60     return 0;
61     
62 }
View Code

洛谷 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\n",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 }
View Code

其实树状数组还有一个强大的功能,那就是求逆序对,然而有时数的大小太大,导致不可以作为数组的下标,怎么办呢。这时就要用到离散化了。什么是离散化呢?就是不改变相对大小的前提下,将分散的数据集中起来,比如说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 }
View Code

还有几道相似的例题:

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 }
View Code

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 }
View Code

 


这里还有一道很有意思的题。

poj2481

我们可以把区间看成横纵坐标,这样题目就变成了数星星 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("\n");
74     }
75 }
View Code

尽管树状数组的用途不如线段树广,但学会树状数组依然是很有必要的,总而言之,树状数组是一个非常有用的数据结构。

 

posted @ 2018-10-31 20:41  snowy2002  阅读(262)  评论(0编辑  收藏  举报