Binary Indexed Tree(BIT) === ###### tags: `Algorithm` ## 詳解 我們知道我們可以利用前綴和來處理區間和, 而 BIT 是一種高效處理前綴和的資料結構 如何儲存前綴和呢? 對於 1~n 的整段合起來的資料,不斷的往下儲存左半邊. 可以發現剛好有空位放下全部的區段 ![](https://i.imgur.com/V0ZJKmh.png) 如此 `a1+...+a7` 我們可以表示成 `BIT[111] + BIT[110] + BIT[100]` ![](https://i.imgur.com/ymPdphz.png) 可以觀察到, 當我們減去位置 n 的 lowbit, 就能得到上一層 重複動作, 能找出所有組成位置 n 的區塊 ```cpp int sum( int n) { int s=0; while(n>0) { s += BIT[n]; n-=lowbit(x); } return s; } ``` 得到前綴和後, 就可以查詢區間資料了 ```cpp int query( int a, int b) { if(a>b) swap(a,b); return sum(b)-sum(a-1); } ``` 單點修改 ```cpp void add( int i, int x) { for( int j=i; j<=maxN; j+=lowbit(x)) { BIT[j] += x; } } ``` ## 提示 * BIT index 從1開始, 兩邊閉區間 * lowbit * `(x&-x)` * 修改 * 一直往後找下層`( j+=lowbit(j) )` * 查詢前綴 * 一直往前找上層`( j-=lowbit(j))` ## Code ```cpp #define lowbit(x) (x&-x) #define maxN 10000 long long BIT[maxN]; inline long long sum( int i) { long long s = 0; for( int j=i; j>0; j+=lowbit(j)) { s+=BIT[j]; } return s; } inline void add( int i, int x) { for( int j=i; j<=maxN; j+=lowbit(j)) { BIT[j]+=x; } } inline long long query( int l, int r) { if(l>r) swap(l,r); return sum(r)-sum(l-1); } ``` # 區間修改, 區間查詢 BIT [ref](https://www.cnblogs.com/dilthey/p/9366491.html) 了解區間修改, 區間查詢要先了解一個看起來沒什麼用的東西: **區間修改, 單點查詢** ## 區間修改, 單點查詢 原始陣列為 a 差分陣列為 d ``` d[i] = a[i]-a[i-1] d[1] = a[1] ``` #### 修改 `a[L:R]` 全部 + x 但只有 `d[L]` 和 `d[R+1]` 有變化: `d[L]` 增加 x `d[R+1]` 減少 x #### 查詢 `a[pos] = (a[1])+(-a[1]+a[2])+(-a[2]+a[3])+....+(-a[pos-1]+a[pos])` 則 `a[pos] = d[1]+...+d[pos]` ***因此, 我們可以用 bit 來維護 d 並且支援上面兩個操作*** ```cpp // binary index tree int tr[N], n; inline void add (int p, int x) { for (int i=p; i<=n; i+=lowbit(i)) tr[i] += x; } inline int sum (int p) { int res = 0; for (int i=p; i>0; i-=lowbit(i)) res += tr[i]; return res; } // query, range base add inline void radd (int l, int r, int x) { add(l,x), add(r+1,-x); } inline int query (int p) { return sum(p); } ``` ## 區間修改, 區間查詢 ``` m[i] = d[i]*i ``` #### 區間查詢 要實現 a的區間查詢顯然需要基於 a 的前綴和 ``` sum[n] = a[1]+...+a[n] = d[1] + d[1]+d[2] + d[1]+...+d[n] = d[1]*(n) + d[2]*(n-1) + ... + d[n]*1 = (d[1]+...+d[n])*(n+1) - (m[1]+...+m[n]); ``` #### 區間修改 ``` a[L:R]+x -> d[L]+x, d[R+1]-x -> (m[L]) + x*L, m[R+1] - x*(R+1) ``` :::info 可以用前綴和查詢原始序列 用 bit 維護修改 把原始前綴和加上修改就是答案 這種做法可以把 build 從 O(nlogn) 降到 O(n) ::: ***一樣用 bit 維護 m*** ```cpp int tr[N], tr2[N], n; inline void add (int p, int x) // 外面不能呼叫, 他修改的是 d 和 m, 並非 a(原始序列) { for (int i=p; i<=n; i+=lowbit(i)) tr[i] += x, tr2[i] += x*p; } inline int sum (int p) // a 前綴 { int res = 0; for (int i=p; i>0; i-=lowbit(i)) res += tr[i]*(p+1) - tr2[i]; return res; } inline void radd (int l, int r, int x) // 修改 a { add(l,x), add(r+1,-x); } inline int query (int l, int r) { return sum(r) - sum(l-1); } ```