樹狀陣列總結——詳解(單點/區間查詢, 單點/區間修改, 逆序對)
樹狀陣列(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?- /*************************************************************************
- > Author: wzw-cnyali
- > Created Time: 2017/6/13 10:33:11
- ************************************************************************/
- #prag\
- ma GCC optimize("O3")
- #include<iostream>
- #include<cstdio>
- #include<cstdlib>
- #include<cmath>
- #include<cstring>
- #include<algorithm>
- usingnamespace std;
- typedeflonglong LL;
- typedef unsigned longlong uLL;
- #define REP(i, a, b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++ i)
- #define DREP(i, a, b) for(register int i = (a), i##_end_ = (b); i >= i##_end_; -- i)
- #define EREP(i, a) for(register int i = (be[a]); i != -1; i = nxt[i])
- #define debug(...) fprintf(stderr, __VA_ARGS__)
- #define mem(a, b) memset((a), b, sizeof(a))
- template<typename T> inlinebool chkmin(T &a, const T &b) { return a > b ? a = b, 1 : 0; }
- template<typename T> inlinebool chkmax(T &a, const T &b) { return a < b ? a = b, 1 : 0; }
- char buff[1 << 25], *buf = buff;
- template <class T>
- T read(T sum = 0, T fg = 0)
- {
- while(*buf < '0' || *buf > '9') { fg |= *buf == '-'; buf++; }
- while(*buf >= '0' && *buf <= '9') { sum = sum * 10 + *buf - '0'; buf++; }
- return fg ? -sum : sum;
- }
- constint inf = 1e9;
- const LL INF = 1e17;
- constint Size = 500010;
- constint maxn = 100000;
- constint maxm = 100000;
- int n, m;
- int a[Size];
- struct index_tree
- {
- #define lowbit(x) ((x) & (-x))
- void add(int x, int val)
- {
- for(; x <= n; x += lowbit(x)) a[x] += val;
- }
- int query(int x)
- {
- int ans = 0;
- for(; x; x -= lowbit(x)) ans += a[x];
- return ans;
- }
- }tree;
- int main()
- {
- #ifndef ONLINE_JUDGE
- freopen("input.in", "r", stdin);
- freopen("output.out", "w", stdout);
- #endif
- fread(buff, 1, 1 << 25, stdin);
- n = read<int>(), m = read<int>();
- REP(i, 1, n) tree.add(i, read<int>());
- while(m--)
- {
- int tp = read<int>();
- if(tp == 1)
- {
- int x = read<int>(), val = read<int>();
- tree.add(x, val);
- }
- else
- {
- int x = read<int>(), y = read<int>();
- printf("%d\n", tree.query(y) - tree.query(x - 1));
- }
- }
- return 0;
- }
- 區間修改 && 單點查詢
已知一個數列,你需要進行下面兩種操作:
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
- /*************************************************************************
- > Author: wzw-cnyali
- > Created Time: 2017/6/13 11:11:16
- ************************************************************************/
- #include<iostream>
- #include<cstdio>
- #include<cstdlib>
- #include<cmath>
- #include<cstring>
- #include<algorithm>
- #prag\
- ma GCC optimize("O3")
- usingnamespace std;
- typedeflonglong LL;
- typedef unsigned longlong uLL;
- #define REP(i, a, b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++ i)
- #define DREP(i, a, b) for(register int i = (a), i##_end_ = (b); i >= i##_end_; -- i)
- #define EREP(i, a) for(register int i = (be[a]); i != -1; i = nxt[i])
- #define debug(...) fprintf(stderr, __VA_ARGS__)
- #define mem(a, b) memset((a), b, sizeof(a))
- template<typename T> inlinebool chkmin(T &a, const T &b) { return a > b ? a = b, 1 : 0; }
- template<typename T> inlinebool chkmax(T &a, const T &b) { return a < b ? a = b, 1 : 0; }
- char buff[1 << 25], *buf = buff;
- template <class T>
- T Fread(T sum = 0, T fg = 0)
- {
- while(*buf < '0' || *buf > '9') { fg |= *buf == '-'; buf++; }
- while(*buf >= '0' && *buf <= '9') { sum = sum * 10 + *buf - '0'; buf++; }
- return fg ? -sum : sum;
- }
- template <class T>
- T read(T sum = 0, T fg = 0)
- {
- char c = getchar();
- while(c < '0' || c > '9') { fg |= c == '-'; c = getchar(); }
- while(c >= '0' && c <= '9') { sum = sum * 10 + c - '0'; c = getchar(); }
- return fg ? -sum : sum;
- }
- constint inf = 1e9;
- const LL INF = 1e17;
- constint Size = 500010;
- constint maxn = 100000;
- constint maxm = 100000;
- int n, m;
- int a[Size];
- struct index_tree
- {
- #define lowbit(x) ((x) & (-x))
- int tree[Size];
- void add(int x, int val)
- {
- for(; x <= n; x += lowbit(x)) tree[x] += val;
- }
- int query(int x)
- {
- int ans = 0;
- for(; x; x -= lowbit(x)) ans += tree[x];
- return ans;
- }
- }tree;
- int main()
- {
- #ifndef ONLINE_JUDGE
- freopen("input.in", "r", stdin);
- freopen("output.out", "w", stdout);
- #endif
- fread(buff, 1, 1 << 25, stdin);
- n = Fread<int>(), m = Fread<int>();
- REP(i, 1, n)
- {
- a[i] = Fread<int>();
- tree.add(i, a[i] - a[i - 1]);
- }
- while(m--)
- {
- if(Fread<int>() == 1)
- {
- int x = Fread<int>(), y = Fread<int>(), val = Fread<int>();
- tree.add(x, val);
- tree.add(y + 1, -val);
- }
- else printf("%d\n", tree.query(Fread<int>()));
- }
- return 0;
- }
- 區間修改 && 區間查詢
已知一個數列,你需要進行下面兩種操作:
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?- /*************************************************************************
- > Author: wzw-cnyali
- > Created Time: 2017/6/13 15:18:18
- ************************************************************************/
- #include<iostream>
- #include<cstdio>
- #include<cstdlib>
- #include<cmath>
- #include<cstring>
- #include<algorithm>
- #prag\
- ma GCC optimize("O3")
- usingnamespace std;
- typedeflonglong LL;
- typedef unsigned longlong uLL;
- #define REP(i, a, b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++ i)
- #define DREP(i, a, b) for(register int i = (a), i##_end_ = (b); i >= i##_end_; -- i)
- #define EREP(i, a) for(register int i = (be[a]); i != -1; i = nxt[i])
- #define debug(...) fprintf(stderr, __VA_ARGS__)
- #define mem(a, b) memset((a), b, sizeof(a))
- template<typename T> inlinebool chkmin(T &a, const T &b) { return a > b ? a = b, 1 : 0; }
- template<typename T> inlinebool chkmax(T &a, const T &b) { return a < b ? a = b, 1 : 0; }
- char buff[1 << 21], *buf = buff;
- template <class T>
- T Fread(T sum = 0, T fg = 0)
- {
- while(*buf < '0' || *buf > '9') { fg |= *buf == '-'; buf++; }
- while(*buf >= '0' && *buf <= '9') { sum = sum * 10 + *buf - '0'; buf++; }
- return fg ? -sum : sum;
- }
- template <class T>
- T read(T sum = 0, T fg = 0)
- {
- char c = getchar();
- while(c < '0' || c > '9') { fg |= c == '-'; c = getchar(); }
- while(c >= '0' && c <= '9') { sum = sum * 10 + c - '0'; c = getchar(); }
- return fg ? -sum : sum;
- }
- constint inf = 1e9;
- const LL INF = 1e17;
- constint Size = 100010;
- constint maxn = 100000;
- constint maxm = 100000;
- int n, m;
- struct index_tree
- {
- #define lowbit(x) ((x) & (-x))
- void add(LL *array, int x, LL val)
- {
- for(; x <= n; x += lowbit(x)) array[x] += val;
- }
- LL query(LL *array, int x)
- {
- LL ans = 0;
- for(; x; x -= lowbit(x)) ans += array[x];
- return ans;
- }
- }tree;
- LL c1[Size], c2[Size];
- int main()
- {
- #ifndef ONLINE_JUDGE
- freopen("input.in", "r", stdin);
- freopen("output.out", "w", stdout);
- #endif
- fread(buff, 1, 1 << 21, stdin);
- n = Fread<int>(), m = Fread<int>();
- LL last_x = 0;
- REP(i, 1, n)
- {
- LL x = Fread<LL>();
- tree.add(c1, i, x - last_x);
- tree.add(c2, i, (x - last_x) * (i - 1));
- last_x = x;
- }
- while(m--)
- {
- if(Fread<int>() == 1)
- {
- int x = Fread<int>(), y = Fread<int>();
- LL val = Fread<LL>();
- tree.add(c1, x, val); tree.add(c1, y + 1, -val);
- tree.add(c2, x, val * (x - 1)); tree.add(c2, y + 1, -val * y);
- }
- else
- {
- int x = Fread<int>(), y = Fread<int>();
- LL sum1 = (x - 1) * tree.query(c1, x - 1) - tree.query(c2, x - 1);
- LL sum2 = y * tree.query(c1, y) - tree.query(c2, y);
- printf("%lld\n", sum2 - sum1);
- }
- }
- return 0;
- }
- 逆序對
對於一個包含n個非負整數的陣列a,如果有i<j,且a[i]>a[j],則稱(a[i],A[j])為陣列a中的一個逆序對。
即逆序數就是前面比後面大的一對數。
[cpp] view plain copy print?- /*************************************************************************
- > Author: wzw-cnyali
- > Created Time: 2017/6/13 16:59:03
- ************************************************************************/
- #include<iostream>
- #include<cstdio>
- #include<cstdlib>
- #include<cmath>
- #include<cstring>
- #include<algorithm>
- #prag\
- ma GCC optimize("O3")
- usingnamespace std;
- typedeflonglong LL;
- typedef unsigned longlong uLL;
- #define REP(i, a, b) for(register int i = (a), i##_end_ = (b); i <= i##_end_; ++ i)
- #define DREP(i, a, b) for(register int i = (a), i##_end_ = (b); i >= i##_end_; -- i)
- #define EREP(i, a) for(register int i = (be[a]); i != -1; i = nxt[i])
- #define debug(...) fprintf(stderr, __VA_ARGS__)
- #define mem(a, b) memset((a), b, sizeof(a))
- template<typename T> inlinebool chkmin(T &a, const T &b) { return a > b ? a = b, 1 : 0; }
- template<typename T> inlinebool chkmax(T &a, const T &b) { return a < b ? a = b, 1 : 0; }
- char buff[1 << 25], *buf = buff;
- template <class T>
- T Fread(T sum = 0, T fg = 0)
- {
- while(*buf < '0' || *buf > '9') { fg |= *buf == '-'; buf++; }
- while(*buf >= '0' && *buf <= '9') { sum = sum * 10 + *buf - '0'; buf++; }
- return fg ? -sum : sum;
- }
- template <class T>
- T read(T sum = 0, T fg = 0)
- {
- char c = getchar();
- while(c < '0' || c > '9') { fg |= c == '-'; c = getchar(); }
- while(c >= '0' && c <= '9') { sum = sum * 10 + c - '0'; c = getchar(); }
- return fg ? -sum : sum;
- }
- constint inf = 1e9;
- const LL INF = 1e17;
- constint Size = 100000;
- constint maxn = 100000;
- constint maxm = 100000;
- int n;
- struct node
- {
- int val, id;
- }a[Size];
- bool cmp(node a, node b)
- {
- return a.val < b.val;
- }
- struct index_tree
- {
- #define lowbit(x) ((x) & (-x))
- int tree[Size];
- void add(int x)
- {
- for(; x <= n; x += lowbit(x)) tree[x]++;
- }
- int query(int x)
- {
- int ans = 0;
- for(; x; x -= lowbit(x)) ans += tree[x];
- return ans;
- }
- }tree;
- int c[Size];
- int main()
- {
- #ifndef ONLINE_JUDGE
- freopen("input.in", "r", stdin);
- freopen("output.out", "w", stdout);
- #endif
- fread(buff, 1, 1 << 25, stdin);
- n = Fread<int>();
- REP(i, 1, n) a[i] = (node){Fread<int>(), i};
- sort(a + 1, a + n + 1, cmp);
- REP(i, 1, n) c[a[i].id] = i;
- int ans = 0;
- DREP(i, n, 1)
- {
- ans += tree.query(c[i]);
- tree.add(c[i]);
- }
- printf("%d\n", ans);
- return 0;
- }