• 树状数组总结


    树状数组还是挺方便的,代码短功能也强大,完全可以用来替代一部分线段树的功能

    有三种用法

    一是对于单点更新,区间查询的

     1 //hdu 1166
     2 #include <stdio.h>
     3 #include <string.h>
     4 #include <iostream>
     5 
     6 using namespace std;
     7 
     8 int n,t;
     9 int a[50008];
    10 int tree[50008];
    11 
    12 void add(int x,int num)
    13 {
    14     while(x<=n)
    15     {
    16         tree[x]+=num;
    17         x+=x&-x;
    18     }
    19 }
    20 
    21 int read(int x)
    22 {
    23     int sum = 0;
    24     while(x)
    25     {
    26         sum+=tree[x];
    27         x-=x&-x;
    28     }
    29     return sum;
    30 }
    31 
    32 int main()
    33 {
    34     string tmp;
    35     int b,c;
    36     scanf("%d",&t);
    37     for(int j = 1;j<=t;j++)
    38     {
    39         memset(tree,0,sizeof(tree));
    40         scanf("%d",&n);
    41         printf("Case %d:
    ",j);
    42         for(int i = 1;i<=n;i++)
    43         {
    44              scanf("%d",&a[i]);
    45              add(i,a[i]);
    46         }
    47 
    48         while(cin>>tmp,tmp!="End")
    49         {
    50             if(tmp=="Query")
    51             {
    52                 scanf("%d%d",&b,&c);
    53                 printf("%d
    ",read(c)-read(b-1));
    54             }else if(tmp=="Add")
    55             {
    56                 scanf("%d%d",&b,&c);
    57                 add(b,c);
    58             }else if(tmp=="Sub")
    59             {
    60                 scanf("%d%d",&b,&c);
    61                 add(b,-c);
    62             }
    63         }
    64     }
    65     return 0;
    66 }

    二是对于单点更新,但是查询区间最大最小值的

     1 //hdu 1754
     2 #include <stdio.h>
     3 #include <string.h>
     4 #include <iostream>
     5 
     6 using namespace std;
     7 
     8 int n,m;
     9 int a[200008];
    10 int tree[200008];
    11 
    12 int lowbit(int x)
    13 {
    14     return x&-x;
    15 }
    16 
    17 void update(int x)
    18 {
    19     while(x<=m)
    20     {
    21         tree[x]=a[x];
    22         for(int i=1;i<lowbit(x);i<<=1)
    23             tree[x]=max(tree[x],tree[x-i]);
    24         x+=lowbit(x);
    25     }
    26     return ;
    27 }
    28 int query(int x, int y)
    29 {
    30     int ans = 0;
    31     while (y >= x)
    32     {
    33         ans = max(a[y], ans);
    34         y --;
    35         for (; y-lowbit(y) >= x; y -= lowbit(y))
    36             ans = max(tree[y], ans);
    37     }
    38     return ans;
    39 }
    40 
    41 int main()
    42 {
    43     char tmp;
    44     int c,d;
    45     while(scanf("%d%d",&m,&n)!=EOF)
    46     {
    47         memset(tree,0,sizeof(tree));
    48         for(int i = 1;i<=m;i++)
    49         {
    50             scanf("%d",&a[i]);
    51             update(i);
    52         }
    53         getchar();
    54         for(int i = 1 ;i <= n;i++)
    55         {
    56             scanf("%c%d%d",&tmp,&c,&d);
    57             if(tmp=='U')
    58             {
    59                 a[c] = d;
    60                 update(c);
    61             }
    62             else if(tmp=='Q')
    63             {
    64                 printf("%d
    ",query(c,d));
    65             }
    66             getchar();
    67         }
    68     }
    69     return 0;
    70 }

    三是对于区间更新,然后区间查询

    这个区间更新主要是要用到一个差分数组

    我们假设sigma(r,i)表示r数组的前i项和,调用一次的复杂度是log2(i)

    设原数组是a[n],差分数组c[n],c[i]=a[i]-a[i-1],那么明显地a[i]=sigma(c,i),如果想要修改a[i]到a[j](比如+v),只需令c[i]+=v,c[j+1]-=v

    这样c[i]->C[j]便会相当与加了j-i个v

    怎么实现“区间修改,区间查询”呢?

    观察式子:
    a[1]+a[2]+...+a[n]

    = (c[1]) + (c[1]+c[2]) + ... + (c[1]+c[2]+...+c[n]) 

    = n*c[1] + (n-1)*c[2] +... +c[n]

    = n * (c[1]+c[2]+...+c[n]) - (0*c[1]+1*c[2]+...+(n-1)*c[n])  

    那么我们就维护一个数组c2[n],其中c2[i] = (i-1)*c[i]

    每当修改c的时候,就同步修改一下c2,这样复杂度就不会改变

    那么

    式子①

    =n*sigma(c,n) - sigma(c2,n)

    于是我们做到了在O(logN)的时间内完成一次区间和查询

     1 //poj 3468
     2 #include <stdio.h>
     3 #include <string.h>
     4 #include <iostream>
     5 #define Nmax 200008
     6 
     7 using namespace std;
     8 
     9 int n,m;
    10 long long a[200008];
    11 long long tree[200008];
    12 long long sum[200008];    //原始的前缀合
    13 long long c1[Nmax];   //前缀和
    14 long long c2[Nmax];   //前缀和*i-1
    15 
    16 int lowbit(long long x)
    17 {
    18     return x&-x;
    19 }
    20 
    21 void add(long long *array,long long x,long long num)
    22 {
    23     while(x<=n)
    24     {
    25         array[x] += num;
    26         x+=x&-x;
    27     }
    28 }
    29 
    30 
    31 long long read(long long *array,long long x)
    32 {
    33     long long sum = 0;
    34     while(x)
    35     {
    36         sum += array[x];
    37         x-=x&-x;
    38     }
    39     return sum;
    40 }
    41 
    42 int main()
    43 {
    44     scanf("%d%d",&n,&m);
    45     char tmp;
    46     long long  l,r,q;
    47     for(int i = 1;i<=n;i++)
    48     {
    49         scanf("%lld",&a[i]);
    50         add(c1,i,a[i]-a[i-1]);
    51         add(c2,i,(i-1)*(a[i]-a[i-1]));
    52     }
    53     getchar();
    54     for(int i = 1;i<=m;i++)
    55     {
    56         scanf("%c",&tmp);
    57         if(tmp=='C')
    58         {
    59             scanf("%lld%lld%lld",&l,&r,&q);
    60             add(c1,l,q);
    61             add(c1,r+1,-q);
    62             add(c2,l,(l-1)*q);
    63             add(c2,r+1,-q*r);
    64         }else if(tmp=='Q')
    65         {
    66             scanf("%lld%lld",&l,&r);
    67             long long ans1 = (l-1)*read(c1,l-1)-read(c2,l-1);
    68             long long ans2 = (r)*read(c1,r)-read(c2,r);
    69             printf("%lld
    ",ans2-ans1);
    70         }
    71         getchar();
    72     }
    73     return 0;
    74 }

    四、利用树状数组来求第k小或者第k大

    其主要的想法就是利用树状数组来统计每个数出现的次数

    然后在通过枚举二进制的方式来进行统计,复杂度是log(n)

    详情看代码理解

    这个代码是第K大的,如果要求第K小的,那么枚举的时候反过来即可,以及判断条件改一下就OK

    如果想取第几名的话,那么改插入的条件,每个值只能插入一次即可。

     1 #include <stdio.h>
     2 #include <string.h>
     3 #define maxn 100000
     4 
     5 int tree[maxn],n,k;    //tree是统计次数,每个数字出现的次数
     6 
     7 int lowbit(int x)
     8 {
     9     return x&-x;
    10 }
    11 
    12 void add(int x, int num)
    13 {
    14     while (x <= maxn)
    15     {
    16         tree[x] += num;
    17         x += lowbit(x);
    18     }
    19 }
    20 
    21 int find(int k)     //找第k大
    22 {
    23     int cnt = 0, ans = 0;
    24     for (int i = 20; i >= 0; i--)       //枚举二进制数
    25     {
    26         ans += (1 << i);
    27         if (ans >= maxn|| cnt + tree[ans] >= k)     //如果ans超过最大值或者累加值加起来超过了k,那么说明这个位置的肯定在k的后面
    28             ans -= (1 << i);
    29         else
    30             cnt += tree[ans];     //说明第k肯定在ans的后面
    31     }
    32     return ans + 1;
    33 }
    34 
    35 void input() {
    36     memset(tree, 0, sizeof(tree));
    37     int t;
    38     scanf("%d%d", &n, &k);
    39     for (int i = 0; i<n; i++) {
    40         scanf("%d", &t);
    41         add(t, 1);                       //t位置的次数加一
    42     }
    43     printf("%d/n", find(k));
    44 }
  • 相关阅读:
    python的正则表达式 re-------可以在字符串前加上 r 这个前缀来避免部分疑惑,因为 r 开头的python字符串是 raw 字符串,所以里面的所有字符都不会被转义
    mysql中max_allowed_packet参数的配置方法(避免大数据写入或者更新失败)
    提交 git 项目 到 github 在 centos 7
    常用正则表达式大全 (转)
    如何让vim编辑器永久显示行号
    Ubuntu下忘记MySQL密码重设方法
    Base64复习
    好用的正则表达式工具
    64位centos下安装python的PIL模块
    springboot中使用servlet通过配置类
  • 原文地址:https://www.cnblogs.com/Tree-dream/p/7200337.html
Copyright © 2020-2023  润新知