zoukankan      html  css  js  c++  java
  • 树状数组(入门+一维+二维)

    树状数组

     
    C[i]代表 子树的叶子结点的权值之和
    如图可以知道
    C[1]=A[1];
    C[2]=A[1]+A[2];
    C[3]=A[3];
    C[4]=A[1]+A[2]+A[3]+A[4];
    C[5]=A[5];
    C[6]=A[5]+A[6];
    C[7]=A[7];
    C[8]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8];
    将C[]数组的结点序号转化为二进制
    1=(001)      C[1]=A[1];
    2=(010)      C[2]=A[1]+A[2];
    3=(011)      C[3]=A[3];
    4=(100)      C[4]=A[1]+A[2]+A[3]+A[4];
    5=(101)      C[5]=A[5];
    6=(110)      C[6]=A[5]+A[6];
    7=(111)      C[7]=A[7];
    8=(1000)    C[8]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8];
    对照式子可以发现  C[i]=A[i-2^k+1]+A[i-2^k+2]+......A[i]; (k为i的二进制中从最低位到高位连续零的长度)例如i=8时,k=3;
    现在引入lowbit(x) 
    lowbit(x) 其实就是取出x的最低位1  换言之  lowbit(x)=2^k  k的含义与上面相同 
    C[i]=A[i-2^k+1]+A[i-2^k+2]+......A[i];
    C[i]=A[i-lowbit(i)+1]+A[i-lowbit(i)+2]+......A[i];
     
    区间查询
    下面利用C[i]数组,求A数组中前i项的和 
    举个例子 i=7;
    sum[7]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7] ;   前i项和
    C[4]=A[1]+A[2]+A[3]+A[4];   C[6]=A[5]+A[6];   C[7]=A[7];
    可以推出:   sum[7]=C[4]+C[6]+C[7];
    序号写为二进制: sum[(111)]=C[(100)]+C[(110)]+C[(111)];
     
    再举个例子 i=5
    sum[7]=A[1]+A[2]+A[3]+A[4]+A[5] ;   前i项和
    C[4]=A[1]+A[2]+A[3]+A[4];   C[5]=A[5];
    可以推出:   sum[5]=C[4]+C[5];
    序号写为二进制: sum[(101)]=C[(100)]+C[(101)];
     
    细细观察二进制 树状数组追其根本就是二进制的应用
    int getsum(int x)
    {
        int ans=0;
        for(int i=x;i>0;i-=lowbit(i))
        ans+=C[i];
        return ans;
    }
    单点更新
     
    当我们修改A[]数组中的某一个值时  应当如何更新C[]数组呢?
    回想一下 区间查询的过程,再看一下上文中列出的图
    void add(int x,int y)
    {
        for(int i=x;i<=n;i+=lowbit(i))
        tree[i]+=y;
    }
    //可以发现 更新过程是查询过程的逆过程
    //由叶子结点向上更新C[]数组

    当更新A[1]时  需要向上更新C[1] ,C[2],C[4],C[8]
                         C[1],   C[2],    C[4],     C[8]
    写为二进制  C[(001)],C[(010)],C[(100)],C[(1000)]
                              1(001)     C[1]+=A[1]
    lowbit(1)=001 1+lowbit(1)=2(010)     C[2]+=A[1]
    lowbit(2)=010 2+lowbit(2)=4(100)     C[4]+=A[1]
    lowbit(4)=100 4+lowbit(4)=8(1000)    C[8]+=A[1]
    例题:Ultra-QuickSort  
     In this problem, you have to analyze a particular sorting algorithm. The algorithm processes a sequence of n distinct integers by swapping two adjacent sequence elements until the sequence is sorted in ascending order. For the input sequence 
    9 1 0 5 4 ,

    Ultra-QuickSort produces the output 
    0 1 4 5 9 .

    Your task is to determine how many swap operations Ultra-QuickSort needs to perform in order to sort a given input sequence.InputThe input contains several test cases. Every test case begins with a line that contains a single integer n < 500,000 -- the length of the input sequence. Each of the the following n lines contains a single integer 0 ≤ a[i] ≤ 999,999,999, the i-th input sequence element. Input is terminated by a sequence of length n = 0. This sequence must not be processed.OutputFor every input sequence, your program prints a single line containing an integer number op, the minimum number of swap operations necessary to sort the given input sequence.Sample Input
    5
    9
    1
    0
    5
    4
    3
    1
    2
    3
    0
    
    Sample Output
    6
    0
     1 #include<iostream>
     2 #include<cstdio>
     3 #include<cstring>
     4 #include<algorithm>
     5 using namespace std;
     6 const int maxn=1000010;
     7 int t[maxn];
     8 int lowbit(int x)
     9 {
    10     return x&(-x);
    11 }
    12 void update(int pos)
    13 {
    14     while(pos<maxn)
    15     {
    16         ++t[pos];
    17         pos+=lowbit(pos);
    18     }
    19 }
    20 long long query(int pos)
    21 {
    22     long long cnt=0;
    23     while(pos>0)
    24     {
    25         cnt+=t[pos];
    26         pos-=lowbit(pos);
    27     }
    28     return cnt;
    29 }
    30 int main()
    31 {
    32     int n;
    33     while(~scanf("%d",&n))
    34     {
    35         if(n==0)
    36             break;
    37         memset(t,0,sizeof(t));
    38         long long sum=0;
    39         for(int i=0;i<n;i++)
    40         {
    41             int temp;
    42             scanf("%d",&temp);
    43             int count=query(temp+1);//temp+1防止出现0造成死循环 
    44             sum+=i-count;//count求出的是该数前面出现的比它小的数的个数,而本题让求逆序数,故用i-count 
    45             update(temp+1);
    46         }
    47         printf("%lld
    ",sum);
    48     }
    49 return 0;
    50 }

    二维树状数组 单点更新 区间查询

    设原始二维数组为: 
    A[][]={{a11,a12,a13,a14,a15,a16,a17,a18,a19}, 
    {a21,a22,a23,a24,a25,a26,a27,a28,a29}, 
    {a31,a32,a33,a34,a35,a36,a37,a38,a39}, 
    {a41,a42,a43,a44,a45,a46,a47,a48,a49}};

    记: 
    B[1]={a11,a11+a12,a13,a11+a12+a13+a14,a15,a15+a16,…} 这是第一行的一维树状数组 
    B[2]={a21,a21+a22,a23,a21+a22+a23+a24,a25,a25+a26,…} 这是第二行的一维树状数组 
    B[3]={a31,a31+a32,a33,a31+a32+a33+a34,a35,a35+a36,…} 这是第三行的一维树状数组 
    B[4]={a41,a41+a42,a43,a41+a42+a43+a44,a45,a45+a46,…} 这是第四行的一维树状数组

    那么: 
    C[1][1]=a11,C[1][2]=a11+a12,C[1][3]=a13,C[1][4]=a11+a12+a13+a14,c[1][5]=a15,C[1][6]=a15+a16,… 这是A[][]第一行的一维树状数组 
    C[2][1]=a11+a21,C[2][2]=a11+a12+a21+a22,C[2][3]=a13+a23,C[2][4]=a11+a12+a13+a14+a21+a22+a23+a24,C[2][5]=a15+a25,C[2][6]=a15+a16+a25+a26,… 这是A[][]数组第一行与第二行相加后的树状数组 
    C[3][1]=a31,C[3][2]=a31+a32,C[3][3]=a33,C[3][4]=a31+a32+a33+a34,C[3][5]=a35,C[3][6]=a35+a36,…这是A[][]第三行的一维树状数组 
    C[4][1]=a11+a21+a31+a41,C[4][2]=a11+a12+a21+a22+a31+a32+a41+a42,C[4][3]=a13+a23+a33+a43,… 这是A[][]数组第一行+第二行+第三行+第四行后的树状数组

    二维数组的规律就是,不管是横坐标还是纵坐标,将他们单独拿出来,他们都符合x += lowbit(x),属于它的父亲节点,即都符合那个经典的图。比如C[4][2]: 
    单独看横坐标(就是把纵坐标去掉,重复的只算一个):C[4] = C[2]+C[3]+a[4] (经典图得出)= a[1]+a[2]+a[3]+a[4] (从上面公式得出) 
    单独看纵坐标(就是把横坐标去掉,重复的只算一个):C[2] = C[1]+a[2] (经典图得出)= a[1]+a[2] (上面公式得出)

    还不明白就多看几遍,体会一下,应该就懂了。

    区间查询的话,调用一次query(x,y)显然是求(1,1) 到 (x,y)范围内矩形的和。观察下图,我们没办法一次查询出来给定区间的值,但是我们可以通过计算得出。 
    现在如果要查询蓝色范围内的和 
    调用A点是求紫色边框 
    调用B点是求绿色边框 
    调用C点是求黄色边框 
    调用D点是求红色边框 
    那么A-B-C+D即是答案。 

    接下来以hdu2642为模板题来写一下二维树状数组的单点更新,区间查询。 
    题意:一个星空,二维的。上面有1000*1000的格点,每个格点上有星星在闪烁。一开始时星星全部暗淡着,有M个操作: 
    B x y 点亮一盏星星 
    D x y 熄灭一盏星星 
    Q x1 x2 y1 y2 查询这个矩形里面亮着的星星的个数。

    题解:这就是个二维树状数组单点更新、区间查询的模板题,直接写即可。不过有几个需要注意的地方。就像我在一维里说的,树状数组下标是从1开始维护的,所以我们要把数据偏移到下标从1开始,二维里也是一样。这里坐标可能为0,所以我们把每个坐标都++,然后就是点亮的星星不能重复点亮,暗淡的星星不能重复暗淡,设一个状态的数组即可。

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<cstring>
     4 #include<algorithm>
     5 using namespace std;
     6 const int maxn=1100;
     7 int sz[maxn][maxn],status[maxn][maxn];
     8 int lowbit(int x)
     9 {
    10     return x&(-x);
    11 }
    12 void update(int x,int y,int val)
    13 {
    14     for(int i=x;i<maxn;i+=lowbit(i))
    15     {
    16         for(int j=y;j<maxn;j+=lowbit(j))
    17         {
    18             sz[i][j]+=val;
    19         }
    20     }
    21 }
    22 int query(int x,int y)
    23 {
    24     int ans=0;
    25     for(int i=x;i>0;i-=lowbit(i))
    26     {
    27         for(int j=y;j>0;j-=lowbit(j))
    28         {
    29             ans+=sz[i][j];
    30         }
    31     }
    32     return ans;
    33 } 
    34 int main()
    35 {
    36     int T;
    37     cin>>T;
    38     memset(sz,0,sizeof(sz));
    39     memset(status,0,sizeof(status));
    40     char ch;
    41     int x1,x2,y1,y2;
    42     while(T--)
    43     {
    44         getchar();
    45         scanf("%c",&ch);
    46         if(ch=='B')
    47         {
    48             scanf("%d%d",&x1,&y1);
    49             x1++;
    50             y1++;
    51             if(status[x1][y1]==0)
    52             {
    53                 update(x1,y1,1);
    54                 status[x1][y1]=1;
    55             }
    56         }
    57         if(ch=='D')
    58         {
    59             scanf("%d%d",&x1,&y1);
    60             x1++;
    61             y1++;
    62             if(status[x1][y1]>0)
    63             {
    64                 update(x1,y1,-1);
    65                 status[x1][y1]=0;
    66             }
    67         }
    68         if(ch=='Q')
    69         {
    70             scanf("%d%d%d%d",&x1,&x2,&y1,&y2);
    71             x1++; y1++;
    72             x2++; y2++;
    73             if(x2<x1)
    74             {
    75                 int t=x2;
    76                 x2=x1;
    77                 x1=t;
    78             }
    79             if(y2<y1)
    80             {
    81                 int t=y2;
    82                 y2=y1;
    83                 y1=t;
    84             }
    85           printf("%d
    ",query(x2,y2)-query(x2,y1-1)-query(x1-1,y2)+query(x1-1,y1-1)); 
    86         }
    87     } 
    88 }

    参考博客:

    http://www.cnblogs.com/hsd-/p/6139376.html

    https://blog.csdn.net/WilliamSun0122/article/details/71642358

  • 相关阅读:
    C++ 类型别名typedef和using
    网页布局及响应式
    js上传图片预览
    bootstrap中-webkit-text-size-adjust :not()
    开始
    通过setDB2Client*来方便的使用TRACE调优jdbc程序
    关于携程的信息泄漏
    如何设置DB2I(SPUFI)来正常工作
    Limit the query running time with Resource limit facility (RLF)
    z/os上的tar和gzip(3)
  • 原文地址:https://www.cnblogs.com/1013star/p/9448480.html
Copyright © 2011-2022 走看看