1. 程式人生 > >樹狀陣列總結——詳解(單點/區間查詢, 單點/區間修改, 逆序對)

樹狀陣列總結——詳解(單點/區間查詢, 單點/區間修改, 逆序對)

64人閱讀 評論(0)收藏舉報 1、概述

  樹狀陣列(binary indexed tree),是一種設計新穎的陣列結構,它能夠高效地獲取陣列中連續n個數的和。概括說,樹狀陣列通常用於解決以下問題:陣列{a}中的元素可能不斷地被修改,怎樣才能快速地獲取連續幾個數的和?

2、樹狀陣列基本操作

  傳統陣列(共n個元素)的元素修改和連續元素求和的複雜度分別為O(1)和O(n)。樹狀陣列通過將線性結構轉換成偽樹狀結構(線性結構只能逐個掃描元素,而樹狀結構可以實現跳躍式掃描),使得修改和求和複雜度均為O(lgn),大大提高了整體效率。

給定序列(數列)A,我們設一個數組C滿足

C[i] = A[i–2^k+ 1] + … + A[i]

其中,k為i在二進位制下末尾0的個數,i從1開始算!

則我們稱C為樹狀陣列。

下面的問題是,給定i,如何求2^k?

答案很簡單:2^k=i&(i^(i-1)) ,也就是i&(-i)

下面進行解釋:

以i=6為例(注意:a_x表示數字a是x進製表示形式):

(i)_10 = (0110)_2

(i-1)_10=(0101)_2

i xor (i-1) =(0011)_2

i and (i xor (i-1))  =(0010)_2

2^k = 2

C[6] = C[6-2+1]+…+A[6]=A[5]+A[6]

 陣列C的具體含義如下圖所示:

  當我們修改A[i]的值時,可以從C[i]往根節點一路上溯,調整這條路上的所有C[]即可,這個操作的複雜度在最壞情況下就是樹的高度即O(logn)。另外,對於求數列的前n項和,只需找到n以前的所有最大子樹,把其根節點的C加起來即可。不難發現,這些子樹的數目是n在二進位制時1的個數,或者說是把n展開成2的冪方和時的項數,因此,求和操作的複雜度也是O(logn)。

  樹狀陣列能快速求任意區間的和:A[i] + A[i+1] + … + A[j],設sum(k) = A[1]+A[2]+…+A[k],則A[i] + A[i+1] + … + A[j] = sum(j)-sum(i-1)。

  • 單點修改  &&  區間查詢

         已知一個數列,你需要進行下面兩種操作:

                   1.將某一個數加上x

                   2.求出某區間每一個數的和

[cpp] view plain copy print?
  1. /************************************************************************* 
  2.      > Author: wzw-cnyali 
  3.      > Created Time: 2017/6/13 10:33:11 
  4.  ************************************************************************/
  5. #prag\
  6. ma GCC optimize("O3")  
  7. #include<iostream>
  8. #include<cstdio>
  9. #include<cstdlib>
  10. #include<cmath>
  11. #include<cstring>
  12. #include<algorithm>
  13. usingnamespace std;  
  14. typedeflonglong LL;  
  15. typedef unsigned longlong uLL;  
  16. #define REP(i, a, b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++ i)
  17. #define DREP(i, a, b) for(register int i = (a), i##_end_ = (b); i >= i##_end_; -- i)
  18. #define EREP(i, a) for(register int i = (be[a]); i != -1; i = nxt[i])
  19. #define debug(...) fprintf(stderr, __VA_ARGS__)
  20. #define mem(a, b) memset((a), b, sizeof(a))
  21. template<typename T> inlinebool chkmin(T &a, const T &b) { return a > b ? a = b, 1 : 0; }  
  22. template<typename T> inlinebool chkmax(T &a, const T &b) { return a < b ? a = b, 1 : 0; }  
  23. char buff[1 << 25], *buf = buff;  
  24. template <class T>  
  25. T read(T sum = 0, T fg = 0)  
  26. {  
  27.     while(*buf < '0' || *buf > '9') { fg |= *buf == '-'; buf++; }  
  28.     while(*buf >= '0' && *buf <= '9') { sum = sum * 10 + *buf - '0'; buf++; }  
  29.     return fg ? -sum : sum;  
  30. }  
  31. constint inf = 1e9;  
  32. const LL INF = 1e17;  
  33. constint Size = 500010;  
  34. constint maxn = 100000;  
  35. constint maxm = 100000;  
  36. int n, m;  
  37. int a[Size];  
  38. struct index_tree  
  39. {  
  40. #define lowbit(x) ((x) & (-x))
  41.     void add(int x, int val)  
  42.     {  
  43.         for(; x <= n; x += lowbit(x)) a[x] += val;  
  44.     }  
  45.     int query(int x)  
  46.     {  
  47.         int ans = 0;  
  48.         for(; x; x -= lowbit(x)) ans += a[x];  
  49.         return ans;  
  50.     }  
  51. }tree;  
  52. int main()  
  53. {  
  54. #ifndef ONLINE_JUDGE
  55.     freopen("input.in""r", stdin);  
  56.     freopen("output.out""w", stdout);  
  57. #endif
  58.     fread(buff, 1, 1 << 25, stdin);  
  59.     n = read<int>(), m = read<int>();  
  60.     REP(i, 1, n) tree.add(i, read<int>());  
  61.     while(m--)  
  62.     {  
  63.         int tp = read<int>();  
  64.         if(tp == 1)  
  65.         {  
  66.             int x = read<int>(), val = read<int>();  
  67.             tree.add(x, val);  
  68.         }  
  69.         else
  70.         {  
  71.             int x = read<int>(), y = read<int>();  
  72.             printf("%d\n", tree.query(y) - tree.query(x - 1));  
  73.         }  
  74.     }  
  75.     return 0;  
  76. }  
題目連結:https://www.luogu.org/problem/show?pid=3374
  • 區間修改  &&  單點查詢

                已知一個數列,你需要進行下面兩種操作:

                        1.將某區間每一個數數加上x

                        2.求出某一個數的值

               思路:

        這裡要引入差分陣列這種東西,我們記d[i] = a[i] - a[i-1](a為原陣列),這樣我們記sigma(d[i]) = a[i] ,為什麼呢,觀察式子sigma(d[i]) = a[1] + a[2] - a[1] +a[3]...這樣一直下去就得到了我們的原陣列。
有什麼用呢?如果我們往一段區間上加k,在差分陣列上如何體現呢?我們舉個例子:
                       a:1,2,3,4,5
                       d:1,1,1,1,1
                       2~4加1
        如果我們盲目的在2到4上加1,就會發現會影響後面的數(因為是字首和),所以我們在2這個位置加一,用樹狀陣列更新,在5的位置減一用樹狀陣列更新就ok了
                       a:1,3,4,5,5
                       d:1,2,1,1,0

[cpp] view plain copy print?
  1. /************************************************************************* 
  2.      > Author: wzw-cnyali 
  3.      > Created Time: 2017/6/13 11:11:16 
  4.  ************************************************************************/
  5. #include<iostream>
  6. #include<cstdio>
  7. #include<cstdlib>
  8. #include<cmath>
  9. #include<cstring>
  10. #include<algorithm>
  11. #prag\
  12. ma GCC optimize("O3")  
  13. usingnamespace std;  
  14. typedeflonglong LL;  
  15. typedef unsigned longlong uLL;  
  16. #define REP(i, a, b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++ i)
  17. #define DREP(i, a, b) for(register int i = (a), i##_end_ = (b); i >= i##_end_; -- i)
  18. #define EREP(i, a) for(register int i = (be[a]); i != -1; i = nxt[i])
  19. #define debug(...) fprintf(stderr, __VA_ARGS__)
  20. #define mem(a, b) memset((a), b, sizeof(a))
  21. template<typename T> inlinebool chkmin(T &a, const T &b) { return a > b ? a = b, 1 : 0; }  
  22. template<typename T> inlinebool chkmax(T &a, const T &b) { return a < b ? a = b, 1 : 0; }  
  23. char buff[1 << 25], *buf = buff;  
  24. template <class T>  
  25. T Fread(T sum = 0, T fg = 0)  
  26. {  
  27.     while(*buf < '0' || *buf > '9') { fg |= *buf == '-'; buf++; }  
  28.     while(*buf >= '0' && *buf <= '9') { sum = sum * 10 + *buf - '0'; buf++; }  
  29.     return fg ? -sum : sum;  
  30. }  
  31. template <class T>  
  32. T read(T sum = 0, T fg = 0)  
  33. {  
  34.     char c = getchar();  
  35.     while(c < '0' || c > '9') { fg |= c == '-'; c = getchar(); }  
  36.     while(c >= '0' && c <= '9') { sum = sum * 10 + c - '0'; c = getchar(); }  
  37.     return fg ? -sum : sum;  
  38. }  
  39. constint inf = 1e9;  
  40. const LL INF = 1e17;  
  41. constint Size = 500010;  
  42. constint maxn = 100000;  
  43. constint maxm = 100000;  
  44. int n, m;  
  45. int a[Size];  
  46. struct index_tree   
  47. {  
  48. #define lowbit(x) ((x) & (-x))
  49.     int tree[Size];  
  50.     void add(int x, int val)  
  51.     {  
  52.         for(; x <= n; x += lowbit(x)) tree[x] += val;  
  53.     }  
  54.     int query(int x)  
  55.     {  
  56.         int ans = 0;  
  57.         for(; x; x -= lowbit(x)) ans += tree[x];  
  58.         return ans;  
  59.     }  
  60. }tree;  
  61. int main()  
  62. {  
  63. #ifndef ONLINE_JUDGE
  64.     freopen("input.in""r", stdin);  
  65.     freopen("output.out""w", stdout);  
  66. #endif
  67.     fread(buff, 1, 1 << 25, stdin);  
  68.     n = Fread<int>(), m = Fread<int>();  
  69.     REP(i, 1, n)  
  70.     {  
  71.         a[i] = Fread<int>();  
  72.         tree.add(i, a[i] - a[i - 1]);  
  73.     }  
  74.     while(m--)  
  75.     {  
  76.         if(Fread<int>() == 1)  
  77.         {  
  78.             int x = Fread<int>(), y = Fread<int>(), val = Fread<int>();  
  79.             tree.add(x, val);  
  80.             tree.add(y + 1, -val);  
  81.         }  
  82.         else printf("%d\n", tree.query(Fread<int>()));  
  83.     }  
  84.     return 0;  
  85. }  

  • 區間修改  &&  區間查詢

               已知一個數列,你需要進行下面兩種操作:

                        1.將某區間每一個數加上x

                        2.求出某區間每一個數的和

  首先觀察式子:

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)的時間內完成一次區間和查詢

一件很好的事情就是樹狀陣列的常數比其他NlogN的資料結構小得多,實際上它的計算次數比NlogN要小很多,再加上它程式碼短,是OI中的利器

[cpp] view plain copy print?
  1. /************************************************************************* 
  2.      > Author: wzw-cnyali 
  3.      > Created Time: 2017/6/13 15:18:18 
  4.  ************************************************************************/
  5. #include<iostream>
  6. #include<cstdio>
  7. #include<cstdlib>
  8. #include<cmath>
  9. #include<cstring>
  10. #include<algorithm>
  11. #prag\
  12. ma GCC optimize("O3")  
  13. usingnamespace std;  
  14. typedeflonglong LL;  
  15. typedef unsigned longlong uLL;  
  16. #define REP(i, a, b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++ i)
  17. #define DREP(i, a, b) for(register int i = (a), i##_end_ = (b); i >= i##_end_; -- i)
  18. #define EREP(i, a) for(register int i = (be[a]); i != -1; i = nxt[i])
  19. #define debug(...) fprintf(stderr, __VA_ARGS__)
  20. #define mem(a, b) memset((a), b, sizeof(a))
  21. template<typename T> inlinebool chkmin(T &a, const T &b) { return a > b ? a = b, 1 : 0; }  
  22. template<typename T> inlinebool chkmax(T &a, const T &b) { return a < b ? a = b, 1 : 0; }  
  23. char buff[1 << 21], *buf = buff;  
  24. template <class T>  
  25. T Fread(T sum = 0, T fg = 0)  
  26. {  
  27.     while(*buf < '0' || *buf > '9') { fg |= *buf == '-'; buf++; }  
  28.     while(*buf >= '0' && *buf <= '9') { sum = sum * 10 + *buf - '0'; buf++; }  
  29.     return fg ? -sum : sum;  
  30. }  
  31. template <class T>  
  32. T read(T sum = 0, T fg = 0)  
  33. {  
  34.     char c = getchar();  
  35.     while(c < '0' || c > '9') { fg |= c == '-'; c = getchar(); }  
  36.     while(c >= '0' && c <= '9') { sum = sum * 10 + c - '0'; c = getchar(); }  
  37.     return fg ? -sum : sum;  
  38. }  
  39. constint inf = 1e9;  
  40. const LL INF = 1e17;  
  41. constint Size = 100010;  
  42. constint maxn = 100000;  
  43. constint maxm = 100000;  
  44. int n, m;  
  45. struct index_tree   
  46. {  
  47. #define lowbit(x) ((x) & (-x))
  48.     void add(LL *array, int x, LL val)  
  49.     {  
  50.         for(; x <= n; x += lowbit(x)) array[x] += val;  
  51.     }  
  52.     LL query(LL *array, int x)  
  53.     {  
  54.         LL ans = 0;  
  55.         for(; x; x -= lowbit(x)) ans += array[x];  
  56.         return ans;  
  57.     }  
  58. }tree;  
  59. LL c1[Size], c2[Size];  
  60. int main()  
  61. {  
  62. #ifndef ONLINE_JUDGE
  63.     freopen("input.in""r", stdin);  
  64.     freopen("output.out""w", stdout);  
  65. #endif
  66.     fread(buff, 1, 1 << 21, stdin);  
  67.     n = Fread<int>(), m = Fread<int>();  
  68.     LL last_x = 0;  
  69.     REP(i, 1, n)  
  70.     {  
  71.         LL x = Fread<LL>();  
  72.         tree.add(c1, i, x - last_x);  
  73.         tree.add(c2, i, (x - last_x) * (i - 1));  
  74.         last_x = x;  
  75.     }  
  76.     while(m--)  
  77.     {  
  78.         if(Fread<int>() == 1)  
  79.         {  
  80.             int x = Fread<int>(), y = Fread<int>();  
  81.             LL val = Fread<LL>();  
  82.             tree.add(c1, x, val); tree.add(c1, y + 1, -val);  
  83.             tree.add(c2, x, val * (x - 1)); tree.add(c2, y + 1, -val * y);  
  84.         }  
  85.         else
  86.         {  
  87.             int x = Fread<int>(), y = Fread<int>();  
  88.             LL sum1 = (x - 1) * tree.query(c1, x - 1) - tree.query(c2, x - 1);  
  89.             LL sum2 = y * tree.query(c1, y) - tree.query(c2, y);  
  90.             printf("%lld\n", sum2 - sum1);  
  91.         }  
  92.     }  
  93.     return 0;  
  94. }  

  • 逆序對

             對於一個包含n個非負整數的陣列a,如果有i<j,且a[i]>a[j],則稱(a[i],A[j])為陣列a中的一個逆序對。

           即逆序數就是前面比後面大的一對數。

[cpp] view plain copy print?
  1. /************************************************************************* 
  2.      > Author: wzw-cnyali 
  3.      > Created Time: 2017/6/13 16:59:03 
  4.  ************************************************************************/
  5. #include<iostream>
  6. #include<cstdio>
  7. #include<cstdlib>
  8. #include<cmath>
  9. #include<cstring>
  10. #include<algorithm>
  11. #prag\
  12. ma GCC optimize("O3")  
  13. usingnamespace std;  
  14. typedeflonglong LL;  
  15. typedef unsigned longlong uLL;  
  16. #define REP(i, a, b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++ i)
  17. #define DREP(i, a, b) for(register int i = (a), i##_end_ = (b); i >= i##_end_; -- i)
  18. #define EREP(i, a) for(register int i = (be[a]); i != -1; i = nxt[i])
  19. #define debug(...) fprintf(stderr, __VA_ARGS__)
  20. #define mem(a, b) memset((a), b, sizeof(a))
  21. template<typename T> inlinebool chkmin(T &a, const T &b) { return a > b ? a = b, 1 : 0; }  
  22. template<typename T> inlinebool chkmax(T &a, const T &b) { return a < b ? a = b, 1 : 0; }  
  23. char buff[1 << 25], *buf = buff;  
  24. template <class T>  
  25. T Fread(T sum = 0, T fg = 0)  
  26. {  
  27.     while(*buf < '0' || *buf > '9') { fg |= *buf == '-'; buf++; }  
  28.     while(*buf >= '0' && *buf <= '9') { sum = sum * 10 + *buf - '0'; buf++; }  
  29.     return fg ? -sum : sum;  
  30. }  
  31. template <class T>  
  32. T read(T sum = 0, T fg = 0)  
  33. {  
  34.     char c = getchar();  
  35.     while(c < '0' || c > '9') { fg |= c == '-'; c = getchar(); }  
  36.     while(c >= '0' && c <= '9') { sum = sum * 10 + c - '0'; c = getchar(); }  
  37.     return fg ? -sum : sum;  
  38. }  
  39. constint inf = 1e9;  
  40. const LL INF = 1e17;  
  41. constint Size = 100000;  
  42. constint maxn = 100000;  
  43. constint maxm = 100000;  
  44. int n;  
  45. struct node  
  46. {  
  47.     int val, id;  
  48. }a[Size];  
  49. bool cmp(node a, node b)  
  50. {  
  51.     return a.val < b.val;  
  52. }  
  53. struct index_tree  
  54. {  
  55. #define lowbit(x) ((x) & (-x))
  56.     int tree[Size];  
  57.     void add(int x)  
  58.     {  
  59.         for(; x <= n; x += lowbit(x)) tree[x]++;  
  60.     }  
  61.     int query(int x)  
  62.     {  
  63.         int ans = 0;  
  64.         for(; x; x -= lowbit(x)) ans += tree[x];  
  65.         return ans;  
  66.     }  
  67. }tree;  
  68. int c[Size];  
  69. int main()  
  70. {  
  71. #ifndef ONLINE_JUDGE
  72.     freopen("input.in""r", stdin);  
  73.     freopen("output.out""w", stdout);  
  74. #endif
  75.     fread(buff, 1, 1 << 25, stdin);  
  76.     n = Fread<int>();  
  77.     REP(i, 1, n) a[i] = (node){Fread<int>(), i};  
  78.     sort(a + 1, a + n + 1, cmp);  
  79.     REP(i, 1, n) c[a[i].id] = i;  
  80.     int ans = 0;  
  81.     DREP(i, n, 1)  
  82.     {  
  83.         ans += tree.query(c[i]);  
  84.         tree.add(c[i]);  
  85.     }  
  86.     printf("%d\n", ans);  
  87.     return 0;