# Binary Indexed Tree ![](https://i.redd.it/j7ti7miuwk881.png) 參考[OI Wiki](https://oi-wiki.org/ds/fenwick/) 這是一篇原理以及應用上非常詳盡的題解。 BIT / Fenwick tree / Binary indexed tree 是一個 lightweight 的 range query / point add 的資料結構,在實作難度跟常數上都能做到比線段樹更優,但是是個很玄的資料結構。 兩種操作的 TC 都是 $O(\log n)$ ```cpp= struct BIT { int n; vector<int> T; BIT(int n): n(n), T(n+1) {} BIT(const vector<int>& nums): BIT(nums.size()) { for (int i=1;i<=n;i++) { T[i] = nums[i-1]; int j = i + (i&-i); if (j<=n) T[j] += T[i]; } } void add(int i,int v) { i++; while (i<=n) { T[i] += v; i += i&-i; } } int query(int i) { i++; int ans = 0; while (i) { ans += T[i]; i -= i&-i; } return ans; } }; ``` 還可以寫得更短, ```cpp struct BIT { int n; vector<int> t; BIT(int n): n(n), t(n+1) {} void add(int i,int v) { for (i++;i>0;i+=i&-i) t[i] += v; } int query(int i) { int ans = 0; for (i++;i<=n;i-=i&-i) ans += t[i]; return ans; } }; ``` 我曾痴迷於將他的原理理解透徹,到最後才發現我的腦力還是不夠,Fenwick 的含金量還是太高。不妨看看 Codeforces 紅人 Colin Galen 的影片 [The Black Box Method: How to Learn Hard Concepts Quickly](https://www.youtube.com/watch?v=RDzsrmMl48I) 約在 3 分時就有提到其實連他本人都沒有完全了解原理,但這也不妨礙他的解題。所以在時機成熟前,就把它當黑箱,能拿來解題就行,不需過度執著原理。 ## 二維 query 也就是 https://cses.fi/problemset/task/1739/ 要處理子矩陣 query 以及 point add 作為模板,一棵樹如下 ```cpp= struct BIT { int m, n; vector<vector<int>> t; BIT(int m,int n): m(m), n(n), t(m+1,vector<int>(n+1)) {} void add(int i_,int j_,int v) { for (int i=i_+1;i<=m;i+=i&-i) { for (int j=j_+1;j<=n;j+=j&-j) t[i][j] += v; } } int query(int i_,int j_) { int ans = 0; for (int i=i_+1;i>0;i-=i&-i) { for (int j=j_+1;j>0;j-=j&-j) ans += t[i][j]; } return ans; } int query(int mi,int mj,int Mi,int Mj) { return query(Mi,Mj) + query(mi-1,mj-1) - query(Mi,mj-1) - query(mi-1,Mj); } }; ``` 操作的時間複雜度 $O(\log m \log n)$ ## 一些怎樣都好的細節 一個常見的套路是用 BIT 當權值樹,也就是說(做值域壓縮後)把某個區間裡的數值存進 BIT 裡 就可以解類似 count reversed pair 的題目。 比方說 下面的題目: * [LC reverse-pairs](https://leetcode.com/problems/reverse-pairs/description/) (本題可以用歸併分治[另見 cdq 分治]或 BIT 求解。因為值域壓縮, $\times 2$ 的要求需要用二分搜達成) * [LC count-increasing-quadruplets](https://leetcode.com/problems/count-increasing-quadruplets/description/) (正解是 $O(n^2)$,但可以用 BIT 退而求其次做到 $O(n^2\log n)$) * [LC minimum-inversion-count-in-subarrays-of-fixed-length](https://leetcode.cn/problems/minimum-inversion-count-in-subarrays-of-fixed-length/) (CSES 有根本一樣的[題目](https://cses.fi/problemset/task/3223)) 更甚者,有一個狂人想到了用兩顆 BIT 來解決 RMQ 問題的做法,常數比 segment tree 低得多,有興趣的話可以參見 [Cf 文章](https://codeforces.com/blog/entry/50903)。 ## range add / range query 另外還有一種,使用兩顆 BIT 並且使用差分思想的巧妙方式去實現 $O(\log n)$,從此從 lazy segment tree 中解放,得到更低的常數! but to do ## 有趣的習題 * 證明`add`, `query`的時間複雜度是$O(\log n)$ For `add`, since `i` grows atleast as fast as `lowbit(i)`, and that if `i=lowbit(i)`, `i` will be doubled in each loop, the loop terminates in $\log n$ time. For `query`, in each loop, the last bit of `i` gets removed. There are atmost `2\log(i)` bits to remove. * 證明`query`的有效性。 If `i=1`, we are done. Suppose this hold for any less `i`. Then $$\begin{aligned} query(i) &= T[i] + query(i-lowbit(i)) \\ &= sum(i-lowbit] + sum(0,i-lowbit(i)) \\ &= sum(0,i] \end{aligned}$$ * 闡述 update tree 跟 interrogation tree的差別? * 證明 $O(n)$ build方法之有效性。 我也不會 * 證明`add`的有效性。 * 證明或證偽,在 update tree 中,$i$ 有 $ctz(i)$ 個children,其中 $ctz$ 定義為:"在二進制中,$i$ 前綴零的個數" * 在heap及binary indexed tree中,因實作方式不同均存在 $O(n)$ 的及 $O(n\log n)$ 的構造方式。請比較兩者各自將 $O(n\log n)$ 進步到 $O(n)$ 的原理。