區間問題,例如區間求和、區間最值,有很多資料結構可以選擇。稀疏表、樹狀陣列和線段樹就是解決區間問題比較套路的方法,只要理解了這些資料結構的特徵並且掌握了**模版,遇到可建模成區間求和和區間最值的問題,就可以輕鬆解決了。
稀疏表經常用來處理離線多輪rmq(區間最值)問題。之所以強調多輪,因為多輪的情況下稀疏表的預處理時間$(nlgn)$才能被攤還出去,試想一下只為一次的區間最值查詢建立乙個稀疏表,未免給人雷聲大雨點小的感覺。
先從最簡單暴力的思路開始。因為是離線的問題,可以用乙個矩陣來儲存答案,$matrix[m][n]$為區間$[m, n]$的最值,這樣用起來直接查詢就行了。預處理的時間為$o(n^2)$,查詢時間為$o(1)$,空間複雜度為$o(n^2)$。預處理比較慢且浪費空間,是不是有其他的手段降低一下這兩塊的開銷?
區間最值問題是不是可以通過求解子區間的最值然後整合出結果?顯然可以,這樣我們就沒必要直接給出區間的最值,給出子區間的就可以了,可以壓縮狀態。以區間極小值為例,我們定義$d(a)$表示區間$a$上的最小值,假設有n個區間$a_1,a_2,.....a_n$,滿足對$\forall i:1\leq i\leq n, a_i\cup a=a$,並且$\bigcap_^a_i = a$,那麼$d(a)$可以通過$min(d(a_i),1\leq i\leq n)$得到。可以看出:在最值問題中,子區間的並集要為當前區間,但是對子區間之間的交集並無要求。
最直觀的想法當然是將區間二分,查詢的時候分別求左右區間的最值,就可以得到了這個區間的最值,用$d(l, r)$表示區間$[l, r)$的最值,$min(l, r)=min\, m=(l+r)/2$。發現這一通操作只是稍微改善了一下複雜度係數,數量級並沒有改變。既然對子區間之間的交集沒有要求,那狀態是不是可以繼續壓縮,比如說$d(1,5)=min\;d(1,6)=min\$,這樣$d(1,4)$就可以被復用多次了。怎麼設定才合理呢?既然$o(n^2)$的複雜度不行,那就往$o(nlgn)$考慮一下?乙個節點平均儲存$o(lgn)$個的區間,到這裡差不多大家心裡已經有答案了吧:只需要維護2的冪次長度的區間就可以了。
定義$st(i,j)$表示以$i$開始的,長度為$2^j$的區間的最值,$st(i,j)$可以通過一下公式得到:
$$st(i,j)=min\, j-1)\}$$
用上面的遞推關係進行預處理,查詢的時候用下面的式子:
$$d(l,r)=min\;k=\left \lfloor log(r-l+1) \right \rfloor$$
1class
sparsetable(object):
2def
__init__
(self, nums: list):
3 size, row, col =len(nums), len(nums), 0
4while (1 << (col + 1)) <=size:
5 col += 1
6 self.st = [[0] * col for _ in
range(row)]
7for i in
range(row):
8 self.st[i][0] =nums[0]
9for j in range(1, col):
10for i in
range(row):
11if i + (1 << j) >size:
12break
13 self.st[i][j] = min(self.st[i][j - 1], self.st[i + (1 << (j - 1))][j - 1])
1415
defquery(self, left: int, right: int):
16 k =0
17while (1 << (k + 1)) <= right - left + 1:
18 k += 1
19return min(self.st[left][k], self.st[right - (1 << k) + 1][k])
當問題滿足和rmq問題一樣的區間特性的時候,就可以考慮用稀疏表求解:
樹狀陣列也是乙個區間長度為2的冪次的一種資料結構,和稀疏表不同的是,位置為$i$的節點只維護一段長度為$lowbit(i)$,範圍為$[i - lowbit(i) + 1, i]$的一段區間。翻譯成樹狀陣列好像並不是很貼切,可能只是把這個東西抽象成樹更好理解一點。
樹狀陣列的**比較定式,理解記憶都比較簡單,這裡以求和為例,單獨用一小節整理一下**,下面的例子均可套用。
1class
bit:
2def
__init__
(self, arr: list[int]):
3 self.arr =arr
4 self.bitree = [0] * (len(arr) + 1) #
下標為0的位置沒有意義5#
建立樹狀陣列
6for index, value in
enumerate(arr):
7self.update(index, value)89
defupdate(self, index: int, delta: int):
10"""
11index對應的是arr的下標,delta是增量值
12"""
13 self.arr[index] +=delta
14 pos = index + 1
15while pos 16 self.bitree[pos] +=delta
17 pos +=self._lowbit(pos)
1819
def sum(self, start: int, end: int) ->int:
20"""
21計算arr陣列中[start, end)區間的和
22"""
23return self._sum(end) -self._sum(start)
2425
def _sum(self, pos: int) ->int:
26"""
27計算arr陣列中[0, pos)區間的和
28"""
29 res =0
30while pos >0:
31 res +=self.bitree[pos]
32 pos -=self._lowbit(pos)
33return
res34
35def _lowbit(self, pos: int) ->int:
36return pos & (-pos)
2.3.1 單點更新,區間查詢
2.3.2 區間更新,單點查詢
2.3.3 區間更新,區間查詢介紹lazy思想:lazy-tag思想,記錄每乙個線段樹節點的變化值,當這部分線段的一致性被破壞我們就將這個變化值傳遞給子區間,大大增加了線段樹的效率。
pushup(rt):通過當前節點rt把值遞迴向上更新到根節點
pushdown(rt):通過當前節點rt遞迴向下去更新rt子節點的值
3.2.1 單點更新,區間查詢
3.2.1 區間更新,區間查詢
樹狀陣列和線段樹
主要解決兩個問題 其他問題可以轉化 更新某一點的值 求區間值 時間按複雜度 logn 原陣列a 1 a 2 a n 寫成樹狀陣列c c x x lowbit x x 左開右閉 筆記 主要 const int n int tr n int lowbit int x void add int x,int...
線段樹和樹狀陣列
引入1 有n個數 n 50000 個數,m m 50000 次詢問。每次詢問區間l到r的數的和。要求輸出每一次詢問的結果.分析 1.用字首和問題進行求解 再開乙個陣列 暫且記為b n 設n個數所組成的陣列為a n b i 用來記錄從a 1 到a i 的所有數字的和 即 b 1 a 1 b 2 b 1...
線段樹和樹狀陣列
線段樹 segment tree 和樹狀陣列是兩種常用的資料結構。他們用來維護乙個區間內的操作,可以在 logn 的複雜度上進行查詢和修改。線段樹可以維護對乙個區間的查詢和修改,可以對區間進行分塊查詢,而樹狀陣列是線段樹的閹割版,經常用來區間查詢,但修改只能進行單點修改,經過改造之後可以區間修改,區...