zoukankan      html  css  js  c++  java
  • 树状数组

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

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

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

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

    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
    ",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
    ",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
    ",(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
    ",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
    ",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
    ",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
    ",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
    ",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
    ",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
    ",ask(aa,bb)%2);
    56             }
    57         }
    58         printf("
    ");
    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
    ",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("
    ");
    74     }
    75 }
    View Code

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

  • 相关阅读:
    [hosts]在hosts中屏蔽一级域名和二级域名的写法
    [oracle]查询一个表中数据的插入时间
    [Windows Doc]微软官方文档
    [PL]如果天空是黑暗的,那就摸黑生存
    [LVM]创建LVM卷
    [powershell]获取FCID&Port
    [oracle]解决ora-01034 oracle not available
    [GoogleBlog]new-approach-to-china
    [时钟]配置日期时间并同步到硬件
    [rhel]安装oracle11g
  • 原文地址:https://www.cnblogs.com/snowy2002/p/9885775.html
Copyright © 2011-2022 走看看