線段樹

segment tree

ZKW

迭代式線段樹

建樹

12345678
2468

假設題目是區間最大值:

48
8

建樹

89101112131415
4567

怎麼蓋?

用一維陣列存就好了

23
1

index:

(原陣列)

讀取測資時,\(a[i]\) 的值放在 \(a[i+n]\) 上

而\(a[i]=max(a[i \times 2],a[i \times 2+1]), i < n\)

建樹

	void init(int n){
    for(int i = 0 ; i < n ; i++){
        tr[i+n] = arr[i];
    }
    for(int i = n ; i >= 1 ; i--){
        tr[i] = max(tr[i<<1], tr[i<<1+1]);
    }
}

注意 tr 的大小是 \(2n\)

單點修改

被修改到的都是他的所有祖先

\(a[i]\) 的祖先是 \(a[i/2]\)

所以一路往上修改就好了

89101112131415
4567
23
1

單點修改

void update(int pos, int val){
    pos += n;
    tr[pos] = val;
    for(int i = pos >> 1 ; i >= 1;i >>= 1){
        tr[i] = max(tr[i<<1], tr[i<<1|1]);
    }
}

區間查詢

89101112131415
4567
23
1

左界與右界各有兩種情況

左界為右節點:將\(a[l]\)單獨計算,然後往右推一格 \(l++\)

右界為左節點:將\(a[r]\)單獨計算,然後往左推一格 \(r--\)

區間查詢

89101112131415
4567
23
1

左界為左節點:直接往上推一格 \(l /= 2\)

右界為右節點:直接往上推一格 \(r /= 2\)

區間查詢

89101112131415
4567
23
1

舉個例子

最後 \(l > r \) 時終止迴圈

區間查詢

int query(int l, int r){
    int res = 0;
    l += n;
    r += n;
    for(; l <= r ; l>>=1, r>>=1){
        if(l & 1) {
        	res = max(res, tr[l]);
            l++;
        }
        if(!(r & 1)) {
        	res = max(res, tr[r]);
            r--;
        }
    }
    return res;
}
int query(int l, int r){
    int res = 0;
    l += n;
    r += n;
    for(; l <= r ; l>>=1, r>>=1){
        if(l & 1) {
        	res = max(res, tr[l++]);
        }
        if(!(r & 1)) {
        	res = max(res, tr[r--]);
        }
    }
    return res;
}

這是左閉右閉的寫法

區間查詢

int query(int l, int r){
    int res = 0;
    l += n;
    r += n;
    for(; l < r ; l>>=1, r>>=1){
        if(l & 1) {
        	res = max(res, tr[l]);
            l++;
        }
        if(r & 1) {
            r--;
        	res = max(res, tr[r]);
        }
    }
    return res;
}
int query(int l, int r){
    int res = 0;
    l += n;
    r += n;
    for(; l < r ; l>>=1, r>>=1){
        if(l & 1) {
        	res = max(res, tr[l++]);
        }
        if(r & 1) {
        	res = max(res, tr[--r]);
        }
    }
    return res;
}

這是左閉右開的寫法

題目

遞迴式線段樹

4n

建樹

void build(int s, int t, int p) {
    // 對 [s,t] 區間建立線段樹,當前根的編號為 p
    if (s == t) {
      tree[p] = a[s];
      return;
    }
    int m = s + ((t - s) >> 1); //中點
    build(s, m, p * 2), build(m + 1, t, p * 2 + 1);
    tree[p] = max(tree[p * 2], tree[(p * 2) + 1]);
}

由於遞迴式線段樹會產生無用的葉節點

所以大小要開到 4n

單點修改

void update(int p, int s, int t, int idx, int val) {
	// a[idx] += val, p 為當前節點 index, [s,t]為當前節點區間
    // 1. 終止條件:到達葉節點
    if (s == t) {
        tree[p] += val; // 更新葉節點的值
        return;
    }

    int mid = s + (t - s) / 2;
    int left_child = 2 * p;       // 左節點 index
    int right_child = 2 * p + 1;  // 右節點 index

    if (idx <= mid) {
        update(left_child, s, mid, idx, val);
    } else {
        update(right_child, mid + 1, t, idx, val);
    }

    // 向上更新
    tree[p] = max(tree[left_child], tree[right_child]);
}

他會跑過所有需要更新的點,然後一路從葉節點往上更新回根節點

區間查詢

int query(int l, int r, int s, int t, int p) {
  // [l, r] 為查詢區間, [s, t] 為當前節點包含的區間, p 為當前節點的編號
  if (l <= s && t <= r)
    return tree[p];  // 當前區間為詢問區間的子集時直接返回當前區間的和
  int m = s + ((t - s) >> 1), ans = -(1e9+7);
  if (l <= m) ans = max(ans, query(l, r, s, m, p * 2));
  // 若左節點代表的區間 [s, m] 與詢問區間有交集, 則遞迴查詢左節點
  if (r > m) ans = max(ans, query(l, r, m + 1, t, p * 2 + 1));
  // 若右節點代表的區間 [m + 1, t] 與詢問區間有交集, 則遞歸查詢右節點
  return ans;
}

查詢時,是從根節點一路往下跑

也就是 

query(l, r, 1, n, 1); //1-based

這裡所有的函式,傳入的值都要是1-based

題目

懶標

我好懶我不想這麼快更新

區間修改

題目改成區間和,但是我們有區間改值這一個動作

很明顯你不能直接砸 \(n\) 次單點修改

怎麼做?

懶標

32168947
57​1711
1228
40

從根節點開始

根節點的區間不包含在紅色區間內,左右子節點與紅色區間有交集

如果紅色區間的值要 +3

往下推

根節點的區間不包含在紅色區間內,右子節點與紅色區間有交集

往下推

根節點的區間包含在紅色區間內,更新並打下懶標\((+3)\)

根節點的區間不包含在紅色區間內,左右子節點與紅色區間有交集

往下推

根節點的區間包含在紅色區間內,更新並打下懶標\((+3)\)

(+3)

根節點的區間包含在紅色區間內,更新並打下懶標\((+3)\)

(+3)

(+3)

5131711
5132311
32168977
5131711
5131711

懶標

1228
40

然後向上更新

(+3)

(+3)

(+3)

5132311
32168977
5132314
1837
55

懶標

void push_up(int p){
	int left_child = p * 2, right_child = p * 2 + 1;
	tree[p] = tree[left_child] + tree[right_child];
    return;
}
void update_range(int p, int s, int t, int L, int R, long long val) {
	// p為當前節點,[s,t]為當前節點之區間,[L,R]為修改之區間,val為修改的數值
    // 如果當前區間包含在目標區間內
    if (L <= s && t <= R) {
        // 更新當前節點的區間和:區間長度 * val
        tree[p] += val * (t - s + 1);
        // 打上懶標 (累加)
        lazy[p] += val;
        return;
    }
	
    // 這個東西等等會講
    push_down(p, s, t);
    
    int mid = s + (t - s) / 2;
    int left_child = 2 * p;
    int right_child = 2 * p + 1;

    if (L <= mid) {
        // 目標區間與左子節點有交集
        update_range(left_child, s, mid, L, R, val);
    }
    if (R > mid) {
        // 目標區間與右子節點有交集
        update_range(right_child, mid + 1, t, L, R, val);
    }

    // 向上更新
    push_up(p);
}

帶懶標的區間查詢

32168977
5132314
1837
55

(+3)

(+3)

(+3)

從根節點開始

根節點的區間不包含在紅色區間內,左右子節點與紅色區間有交集

往下推

根節點的區間不包含在紅色區間內,右子節點與紅色區間有交集

往下推

根節點的區間不包含在紅色區間內,左子節點與紅色區間有交集

把懶標往下傳,位置往下推

根節點的區間包含在紅色區間內,回傳自己的值

根節點的區間不包含在紅色區間內,右子節點與紅色區間有交集

往下推

根節點的區間不包含在紅色區間內,左子節點與紅色區間有交集

把懶標往下傳,位置往下推

根節點的區間包含在紅色區間內,回傳自己的值

32498977
3249111277
32498977
32498977
32498977
32498977

帶懶標的區間查詢

void push_down(int node, int start, int end) {
    if (lazy[node] != 0) { // 如果有懶標才需要下傳
        int mid = start + (end - start) / 2;
        int left_child = 2 * node;
        int right_child = 2 * node + 1;

        // 更新左右子節點的區間和
        // 區間內每個數字都加上 lazy[node],所以總和要加上(區間長度 * lazy[node])
        tree[left_child] += lazy[node] * (mid - start + 1);
        tree[right_child] += lazy[node] * (end - mid);

        // 將懶標記累加給左右子節點
        lazy[left_child] += lazy[node];
        lazy[right_child] += lazy[node];

        // 清空當前節點的懶標記
        lazy[node] = 0;
    }
}

long long query_range(int node, int start, int end, int L, int R) {
    // 如果當前區間包含在目標查詢區間內
    if (L <= start && end <= R) {
        return tree[node];
    }

    // 如果當前區間不包含在目標查詢區間內,就把當前節點的懶標下傳,再往下找
    push_down(node, start, end);

    int mid = start + (end - start) / 2;
    int left_child = 2 * node;
    int right_child = 2 * node + 1;
    
    long long sum = 0;

    if (L <= mid) {
        // 目標區間與左子節點有交集
        sum += query_range(left_child, start, mid, L, R);
    }
    if (R > mid) {
        // 目標區間與右子節點有交集
        sum += query_range(right_child, mid + 1, end, L, R);
    }

    return sum;
}

懶標要開的跟線段樹一樣大 (4n)

題目

CSES1651

CSES1735(多個懶標)

CSES1651 解

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5;
long long int tree[N * 4];
long long int lazy[N * 4];
int a[N * 4];
void push_up(int p){
	int left_child = p * 2, right_child = p * 2 + 1;
	tree[p] = tree[left_child] + tree[right_child];
    return;
}
void push_down(int node, int start, int end) {
    if (lazy[node] != 0) { // 如果有懶標才需要下傳
        int mid = start + (end - start) / 2;
        int left_child = 2 * node;
        int right_child = 2 * node + 1;
 
        // 更新左右子節點的區間和
        // 區間內每個數字都加上 lazy[node],所以總和要加上(區間長度 * lazy[node])
        tree[left_child] += lazy[node] * (mid - start + 1);
        tree[right_child] += lazy[node] * (end - mid);
 
        // 將懶標記累加給左右子節點
        lazy[left_child] += lazy[node];
        lazy[right_child] += lazy[node];
 
        // 清空當前節點的懶標記
        lazy[node] = 0;
    }
}
void update_range(int p, int s, int t, int L, int R, long long val) {
	// p為當前節點,[s,t]為當前節點之區間,[L,R]為修改之區間,val為修改的數值
    // 如果當前區間包含在目標區間內
    if (L <= s && t <= R) {
        // 更新當前節點的區間和:區間長度 * val
        tree[p] += val * (t - s + 1);
        // 打上懶標 (累加)
        lazy[p] += val;
        return;
    }
	
    // 這個東西等等會講
    push_down(p, s, t);
    
    int mid = s + (t - s) / 2;
    int left_child = 2 * p;
    int right_child = 2 * p + 1;
 
    if (L <= mid) {
        // 目標區間與左子節點有交集
        update_range(left_child, s, mid, L, R, val);
    }
    if (R > mid) {
        // 目標區間與右子節點有交集
        update_range(right_child, mid + 1, t, L, R, val);
    }
 
    // 向上更新
    push_up(p);
}
long long query_range(int node, int start, int end, int L, int R) {
    // 如果當前區間包含在目標查詢區間內
    if (L <= start && end <= R) {
        return tree[node];
    }
 
    // 如果當前區間不包含在目標查詢區間內,就把當前節點的懶標下傳,再往下找
    push_down(node, start, end);
 
    int mid = start + (end - start) / 2;
    int left_child = 2 * node;
    int right_child = 2 * node + 1;
    
    long long sum = 0;
 
    if (L <= mid) {
        // 目標區間與左子節點有交集
        sum += query_range(left_child, start, mid, L, R);
    }
    if (R > mid) {
        // 目標區間與右子節點有交集
        sum += query_range(right_child, mid + 1, end, L, R);
    }
 
    return sum;
}
void build(int s, int t, int p) {
  // 對 [s,t] 區間建立線段樹,當前根的編號為 p
  if (s == t) {
    tree[p] = a[s];
    return;
  }
  int m = s + ((t - s) >> 1); //中點
  build(s, m, p * 2), build(m + 1, t, p * 2 + 1);
  tree[p] = tree[p * 2] + tree[(p * 2) + 1];
}
int main()
{
    int n, q;
    cin >> n >> q;
    for (int i = 1 ; i <= n ; i++) {
        cin >> a[i];
    }
    build(1, n, 1);
    int m;
    while(q--){
        cin >> m;
        if(m == 1){
            int l, r, v;
            cin >> l >> r >> v;
            update_range(1, 1, n, l, r, v);
        }
        else{
            int p;
            cin >> p;
            cout << query_range(1, 1, n, p, p) << '\n';
        }
    }
    return 0;
}

CSES1735 解

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5;
long long int tree[N * 4];
long long int lazy_add[N * 4];
long long int lazy_set[N * 4];
bool lazy_seted[N * 4];
int a[N * 4];
void push_up(int p){
	int left_child = p * 2, right_child = p * 2 + 1;
	tree[p] = tree[left_child] + tree[right_child];
    return;
}
void push_down(int node, int start, int end) {
    int mid = start + (end - start) / 2;
    int left_child = 2 * node;
    int right_child = 2 * node + 1;
    if (lazy_seted[node]) {
        
        lazy_seted[left_child] = true;
        lazy_seted[right_child] = true;
        
        lazy_add[left_child] = 0;
        lazy_add[right_child] = 0;

        tree[left_child] = lazy_set[node] * (mid - start + 1);
        tree[right_child] = lazy_set[node] * (end - mid);

        lazy_set[left_child] = lazy_set[node];
        lazy_set[right_child] = lazy_set[node];

        lazy_seted[node] = false;
    }
    if (lazy_add[node] != 0) {

        tree[left_child] += lazy_add[node] * (mid - start + 1);
        tree[right_child] += lazy_add[node] * (end - mid);

        if(lazy_seted[left_child]){
            lazy_set[left_child] += lazy_add[node];
        }
        else{
            lazy_add[left_child] += lazy_add[node];
        }
        if(lazy_seted[right_child]){
            lazy_set[right_child] += lazy_add[node];
        }
        else{
            lazy_add[right_child] += lazy_add[node];
        }

        lazy_add[node] = 0;
    }
}
void update_range_add(int p, int s, int t, int L, int R, long long val) {
    if (L <= s && t <= R) {
        tree[p] += val * (t - s + 1);
        // 打上懶標
        if(lazy_seted[p]){
            lazy_set[p] += val;
        }
        else{
            lazy_add[p] += val;
        }
        return;
    }
	
    push_down(p, s, t);
    
    int mid = s + (t - s) / 2;
    int left_child = 2 * p;
    int right_child = 2 * p + 1;

    if (L <= mid) {
        update_range_add(left_child, s, mid, L, R, val);
    }
    if (R > mid) {
        update_range_add(right_child, mid + 1, t, L, R, val);
    }

    push_up(p);
}
void update_range_set(int p, int s, int t, int L, int R, long long val) {
    if (L <= s && t <= R) {
        tree[p] = val * (t - s + 1);
        // 打上懶標
        lazy_set[p] = val;
        lazy_seted[p] = true;
        lazy_add[p] = 0;
        return;
    }
	
    push_down(p, s, t);
    
    int mid = s + (t - s) / 2;
    int left_child = 2 * p;
    int right_child = 2 * p + 1;

    if (L <= mid) {
        update_range_set(left_child, s, mid, L, R, val);
    }
    if (R > mid) {
        update_range_set(right_child, mid + 1, t, L, R, val);
    }

    push_up(p);
}
long long query_range(int node, int start, int end, int L, int R) {
    if (L <= start && end <= R) {
        return tree[node];
    }

    push_down(node, start, end);

    int mid = start + (end - start) / 2;
    int left_child = 2 * node;
    int right_child = 2 * node + 1;
    
    long long sum = 0;

    if (L <= mid) {
        sum += query_range(left_child, start, mid, L, R);
    }
    if (R > mid) {
        sum += query_range(right_child, mid + 1, end, L, R);
    }

    return sum;
}
void build(int s, int t, int p) {
    if (s == t) {
      tree[p] = a[s];
      return;
    }
    int m = s + ((t - s) >> 1);
    build(s, m, p * 2), build(m + 1, t, p * 2 + 1);
    tree[p] = tree[p * 2] + tree[(p * 2) + 1];
}
int main()
{
    cin.sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int n, q;
    cin >> n >> q;
    for (int i = 1 ; i <= n ; i++) {
        cin >> a[i];
    }
    build(1, n, 1);
    int m;
    while(q--){
        cin >> m;
        if(m == 1){
            int l, r, v;
            cin >> l >> r >> v;
            update_range_add(1, 1, n, l, r, v);
        }
        else if(m == 2){
            int l, r, v;
            cin >> l >> r >> v;
            update_range_set(1, 1, n, l, r, v);
        }
        else{
            int l, r;
            cin >> l >> r;
            cout << query_range(1, 1, n, l, r) << '\n';
        }
    }
    return 0;
}

segment tree

By 硼/Boron

segment tree

  • 146