線段樹 Segment tree 1
本次範圍
陣列版線段樹 (區間查詢、單點修改)
用途
對於區間運算 (e.g. 區間和、區間乘積、區間最大/最小) 可以更快速地運算
適合多次查詢時使用
可將複雜度從 變為
結構
存下一個陣列特定區間的運算結果,並可用多個線段整合後表示出所有區段的答案
區間長度是用二分法分出來的
示意圖:
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
通常我們建線段樹時,會以 1-base 操作(即不使用 Seg[0]
),不僅運作程式較快,寫起來也較方便
一個線段假設存於 Seg[n]
,那麼二分後的線段通常會存在 Seg[2*n], Seg[2*n+1]
由示意圖,我們可以知道:segment tree 大小 原陣列大小的 4 倍
操作
- 區間查詢、單點查詢
- 單點修改
- 區間修改 (要用懶標記才比較快,Segment Tree 2 會講)
實作
很多時候是解區間和,因此以下範例為區間和線段樹
建線段樹
從大線段建到小線段比較簡單,因此我們 code
建議也是先使用這種
至於小線段建到大線段,留待各位自行研究。
對於欲操作陣列 v
,使用遞迴建出 v
的線段樹,傳入引數有:
l
:當前線段左界
r
:當前線段右界 (左閉右開,實際上並不包含 v[r]
) 每個線段範圍可表示為
i
:當前線段的 key
當 r - l = 1
時,代表線段寬度剩 ,此時不用在二分下去,需 return
因此:
區間查詢
只要會區間查詢,顯然單點查詢不是問題。
在查詢時,應該有 5
個變數:
ql
:欲查詢之左界
qr
:欲查詢之右界
l
:當前線段之左界
r
:當前線段之右界
i
:當前線段的 key
查詢時,一樣使用遞迴,且
因此可以寫成:
單點修改
若今天沒有「修改」,那麼區間和就可以用前綴和解決,顯然比較輕鬆。
若今天要使 arr[x] = new value
,很簡單,只要查到 存於哪一個 seg[i]
即可
查詢線段的動作和 query()
滿相似的,只是要根據「單點」性質做一些微調
因為改變了 arr[x]
,因此所有包含 arr[x]
的 seg[i]
都必須更新
modify()
和 build()
的最後一行會非常相似。
實作:
整合
陣列版線段樹 (一般情況都是用這個):
#include<bits/stdc++.h>
#define LL long long
using namespace std;
int maxn = 2e5, n;
vector<LL> v(maxn + 1), seg((maxn << 2) + 5);
#define m (l + r) >> 1
void build(int l = 0, int r = n, int i = 1){
if(r - l == 1) {seg[i] = v[l]; return;}
build(l, m, i << 1), build(m, r, i << 1 | 1);
seg[i] = seg[i << 1] + seg[i << 1 | 1];
}
LL query (int ql, int qr, int l = 0, int r = n, int i = 1){
if(qr <= l || r <= ql) return 0LL;
if(ql <= l && r <= qr) return seg[i];
return query(ql, qr, l, m, i << 1) + query(ql, qr, m, r, i << 1 | 1);
}
void modify(int x, LL val, int l = 0, int r = n, int i = 1){
if(x < l || r <= x) return;
if(r - l == 1) {seg[i] = val; return;}
modify(x, val, l, m, i << 1), modify(x, val, m, r, i << 1 | 1);
seg[i] = seg[i << 1] + seg[i << 1 | 1];
}
#undef m
int main(){
cin >> n;
for(int i = 0; i < n; i++) cin >> v[i];
build();
int T, op, l, r, x; LL val;
cin >> T;
while(T--){
cin >> op;
if(op) cin >> l >> r, cout << query(l - 1, r) << '\n';
else cin >> x >> val, modify(x - 1, val);
}
}
指標版:動態開點再說
Image Not Showing
Possible Reasons
- The image file may be corrupted
- The server hosting the image is unavailable
- The image path is incorrect
- The image format is not supported
Learn More →
例題:DDJ a041. 奇怪的老闆
算是一個很經典的線段樹題,建兩個線段樹,
分別為「區間最大值」和「區間最小值」的線段樹,本題輕鬆解決。
code
#include<bits/stdc++.h>
#define LL long long
using namespace std;
constexpr int MxN = 5e4;
vector<int> v(MxN + 1), hseg((MxN << 2) + 5), lseg((MxN << 2) + 5);
#define m ((l + r) >> 1)
void build(int i = 1, int l = 0, int r = MxN) {
if (r - l == 1) {hseg[i] = lseg[i] = v[l]; return;}
build(i << 1, l, m), build(i << 1 | 1, m, r);
hseg[i] = max(hseg[i << 1], hseg[i << 1 | 1]);
lseg[i] = min(lseg[i << 1], lseg[i << 1 | 1]);
}
int hquery(int ql, int qr, int i = 1, int l = 0, int r = MxN) {
if (qr <= l || r <= ql) return 0;
if (ql <= l && r <= qr) return hseg[i];
return max(hquery(ql, qr, i << 1, l, m), hquery(ql, qr, i << 1 | 1, m, r));
}
int lquery(int ql, int qr, int i = 1, int l = 0, int r = MxN) {
if (qr <= l || r <= ql) return 2147483647;
if (ql <= l && r <= qr) return lseg[i];
return min(lquery(ql, qr, i << 1, l, m), lquery(ql, qr, i << 1 | 1, m, r));
}
#undef m
int main() {
cin.tie(nullptr), ios_base::sync_with_stdio(false);
int n, m, l, r;
cin >> n >> m;
for (int x = 0; x < n; x++) cin >> v[x];
build();
while (m--) {
cin >> l >> r;
cout << hquery(l - 1, r) - lquery(l - 1, r) << '\n';
}
return 0;
}