# 樹狀樹組-BIT > [color=blue]快速求取區間和的助手 ## 回顧 還記得前綴和嗎,他也是用來求區間和的,但你應該會發現,若當中有一個值需要被修改,那整列都會被動到。 以下是一個比較表,了解BIT強大之處 | 資料結構 | 查尋區間和 | 單點修改值 | | ------- | ----------- | ----------- | | 純陣列 | $O(n)$ | $O(1)$ | | 前綴和 | $O(1)$ | $O(n)$ | | BIT | $O(log(n))$ | $O(log(n))$ | ## 用途 & 概念 BIT用於在快速求取區間和的同時,又能保證快速修改。感覺像是陣列與前綴和的優點結合下的產物,不過其複雜程度也是三者之中最高的。以下為示意圖,其中數字代表它儲存的區間。 ```graphviz digraph BIT{ nodesep=1 // increases the separation between nodes node [color=Red,fontname=Courier,shape=box] //All nodes will this shape and colour edge [color=Blue, style=dashed] //All the lines look like this "1~8"->{"1~4" "5~6" "7"} "1~4"->{"1~2" "3"} "1~2"->{"1"} "5~6"->{"5"} {rank=same;"1~2" "5~6"} // Put them on the same level {rank=same;"1" "3" "5" "7"} } ``` 如果想要知道`1~5`的和,可以查詢`1~4+5`。以下為查詢所需區間表。 | 1 | 1~2 | 1~3 | 1~4 | 1~5 | 1~6 | 1~7 | 1~8 | |:---:|:---:|:-------:|:---:|:-------:|:---------:|:-------------:|:---:| | 1 | 1~2 | 1~2 & 3 | 1~4 | 1~4 & 5 | 1~4 & 5~6 | 1~4 & 5~6 & 7 | 1~8 | 可以發現,在元素數量為8時,最多會需要查詢3個區間就可以得到`1~n`的值,接著就像前綴和那樣,用`1~R - 1~(L-1)`就可以求出所有區間的和了。 ## 實作 BIT一般都是用陣列實作,用區間最大值作為存放位置(例如`1~4`放在`4`)。那搜尋與修改呢?這部分就比較複雜了,需要了解一些二進位制,如果你已經理解二進位制了,那就繼續往下看吧。 ### 二進位表 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------ | | 000001 | 000010 | 000011 | 000100 | 000101 | 000110 | 000111 | 001000 | ### lowbit lowbit是在二進位下右邊看過來最前面的1,例如:6(000110)就是2(000010),一樣提供一個表 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |:------:| ------ |:------:|:------:|:------:|:------:|:------:|:------:| | 000001 | 000010 | 000011 | 000100 | 000101 | 000110 | 000111 | 001000 | | 000001 | 000010 | 000001 | 000100 | 000001 | 000010 | 000001 | 001000 | ### 查詢 可以發現,$x$重複$-lowbit(x)$會變成0,且途中會經過所有需要的區間。以7為例 ```graphviz digraph BIT{ nodesep=1// increases the separation between nodes node [color=Red,fontname=Courier,shape=box] //All nodes will this shape and colour edge [color=Blue, style=dashed] //All the lines look like this "7(000111) : 7 "->"6(000110) : 5~6 " "6(000110) : 5~6 "->"4(000100) : 1~4 " "4(000100) : 1~4 "->"0(000000) : none" //{rank=same;"7(000111) : 7 " "6(000110) : 5~6 " "4(000100) : 1~4 " "0(000000) : none"} } ``` 只要將所有經過的區間加在一起,就可以得到區間和了。這也呼應為何他查尋的複雜度為$O(log(n))$,因為$n$只會有$log(n)$個$bit$。 ### 修改 修改也有些相似,變成$x$加上$lowbit(x)$,就會將上面有包含到他的區間也都更新到。以三為例 ```graphviz digraph BIT{ nodesep=1// increases the separation between nodes node [color=Red,fontname=Courier,shape=box] //All nodes will this shape and colour edge [color=Blue, style=dashed] //All the lines look like this "3(000011) : 3 "->"4(000100) : 1~4 " "4(000100) : 1~4 "->"8(001000) : 1~8 " } ``` 這樣最多也是$O(log(n))$。也可以發現$lowbit$的重要性。 ### 程式碼 ```cpp= #include<bits/stdc++.h> using namespace std; #define lowbit(x) (x&-x) // 與 int lowbit(x){ return x&-x;} // 用define或函式雖然不會讓你的程式碼寫起來比較快,但可以使其較易於理解。 using ll=long long; ll bt[200010]; ll a[200010]; // build 函式是透過逐個更新陣列的所有元素來建構BIT void build(int n){ for(int i=1;i<=n;++i) for(int x=i;x<200005;x+=lowbit(x)) bt[x]+=a[i]; } // 單點加值 void add(int x,int k){ a[x]+=k; for(int i=x;i<=200005;i+=lowbit(i)) bt[i]+=k; } // 由單點加值衍生出來的單點修改 void modify(int x,int k){ add(x,k-a[i]); } // 搜尋1~x的區間和 ll find_sum(int x){ ll ret=0; for(int i=x;i>0;i-=lowbit(i)) ret+=bt[i]; return ret; } // 用計算(1~r) - (1~(l-1))算出所有區間的區間和 ll query(int l,int r){ return find_sum(r)-find_sum(l-1); } ``` ## 習題 ### Q-13-1 [區域調查(POJ.1195 Mobile phones 改編)](https://zerojudge.tw/ShowProblem?problemid=d796) #### 題目敘述 : 給一個矩陣 T(1,1), T(1,2),.... T(N,M),求 T(x1,y1) 到 T(x2,y2) 的總和 或者是修改 T(x1,y1) 的值 #### 輸入說明 : 每組輸入的第一行會有兩個正整數 N Q ( 1 ≦ N ≦ 250, Q ≦ 50,0000) 接下來會有 N 行,每行上會有 N 個元素 M ( 0 ≦ M ≦ 32767 ) 接下來會有 Q 行,倘若第一個數字為 1,則接下來會有四個數字 x1 , y1 , x2 , y2, 1 ≦ x1 , y1 , x2 , y2 ≦ 250 請輸出元素 $S={( x , y ) | x1 ≦ x ≦ x2, y1 ≦ y ≦ y2 }$符合的所有元素總和 倘若第一個數字為2,則接下來會有三個數字 x1 , y1 , V, 1 ≦ x1 , y1 ≦ 250 , 0 ≦ V ≦ 32767, 請修改 ( x1 , y1 )= V ; 此行不必輸出 #### 輸出說明 : 若為調查,則輸出區域中的元素總和,若為修改,則不必輸出 #### 範例輸入 1 : ``` 5 10 3 2 2 3 7 4 4 0 3 8 2 4 7 2 3 5 9 6 1 4 7 1 7 1 1 2 2 2 1 1 5 4 5 5 2 2 1 7 1 3 2 1 5 1 2 5 4 5 1 1 2 2 1 2 2 2 7 2 4 5 5 1 3 3 4 5 1 4 3 2 2 ``` #### 範例輸出 1 : ``` 2 42 15 13 24 33 ``` --- ### Q-13-2 [2D rank finding problem(98學年度中投區資訊學科能力競賽)](https://zerojudge.tw/ShowProblem?problemid=d847) #### 題目敘述 : 二度空間上的排名計算問題(2D rank finding problem):給定二度平面空間(2D)上的點A = (a1,a2)與點B = (b1,b2),其大小關係定義為若A > B 若且唯若 a1> b1 且 a2 > b2 ;亦即A點在B點的右上方。如下圖中,B >A, C>A, D>A, D>C, D> B。值得注意的是,並非任意兩點均可以決定大小關係,如下圖中的點A與點E,點D與點E等,無法決定這兩點的大小關係故為無法比較(incomparable)。給定N個點(x1,y1), (x2,y2), …, (xn,yn),定義某一個點的排名(rank) 為所給的點集合中,比該點小的點的個數。 設計一個程式,從檔案讀取點的名稱與座標,計算出在所給定的集合中,所有點的排名值。 #### 輸入說明 : 有多組測試資料 每組的第一行有一個數字N ( 1 ≦ N ≦ 10000 ) 接下來會有N行,每行上會有兩個數字 x y ( 1 ≦ x , y ≦ 1000 ) #### 輸出說明 : 請按照輸入的順序,求出對於 ( x , y ) 有多少個點 ( a , b ) 在它的左下方 a < x , b < y #### 範例輸入 1 : ``` 5 961 404 640 145 983 888 539 71 437 532 ``` #### 範例輸出 1 : ``` 2 1 4 0 0 ``` --- ### Q-13-3 [低地距離(2020年10月APCS)](https://zerojudge.tw/ShowProblem?problemid=f315) #### 題目敘述 : 輸入一個長度為 2n 的陣列,其中 1 ~ n 的每個數字都剛好各 2 次。 i 的低窪值的定義是兩個數值為 i 的位置中間,有幾個小於 i 的的數字。 以 $[3, 1, 2, 1, 3, 2]$ 為例,1的低窪值為 0, 2 的低窪值值為 1, 3 的低窪值為 3。 請對於每個 1 ~ n 的數字都求其低窪值(兩個相同的數字之間有幾個數字比它小),輸出低窪值的總和,答案可能會超過 C++ int 的上限。 #### 輸入說明 : 第一行有一個正整數 n 第二行有 2n 個正整數,以空格分隔,保證 1 ~ n 每個數字都恰好出現兩次。 1≤n≤100000 #### 輸出說明 : 輸出 1 ~ n 每個數字的低窪值總和。 #### 範例輸入 1 : ``` 3 3 1 2 1 3 2 ``` #### 範例輸出 1 : ``` 4 ``` > **2022/1/10** > [name=mtmatt] [time=Mon, Jan 10, 2022 21:10 ] [color=#00eeff]