不知道是基礎還是進階資料結構(二)

今天的內容

  • 有懶標的線段樹
  • Sparse Table

Sparse Table

Given an array of \(n\) integers, your task is to process \(q\) queries of the form: what is the minimum value in range \([a,b]\)?

(\(1 \le n,q \le 2 \cdot 10^5\))

最直接的想法: 全部掃過一次

複雜度 \(O(NQ)\) 成功 TLE 穩

  • 考慮一下有點像倍增的預處理
  • 我們定義 \(s_{i, j}\) 代表編號為 \([i, i+2^j)\) 的最小值
  • \(s_{i, t} = min(s_{i, j - 1}, s_{i+2^{j-1}, j-1})\)
for(int i = 0; i < n; ++i) st[i][0] = a[i];
for(int j = 1; j < M; ++j) 
    for(int i = 0; i + (1 << (j - 1)) < n; ++i) 
        st[i][j] = min(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);

預處理

  • 查詢 \([l, r]\) 時,我們可以看成兩個長度是 \(2 ^{\lfloor \log_2 (r−l+1) \rfloor} \) 的區間
  • 令 \(k= \log_2(r - l + 1)\)
  • \(query(l, r)=min(s_{l, k}, s_{r-2^k+1, k})\)
int query(int l, int r) {
    int k = __lg(r - l + 1);
    return min(st[l][k], st[r - (1 << k) + 1][k]);
}

查詢

  • 預處理 :\(O(nlogn)\):
  • 查詢:\(O(1)\)
  • 總時間複雜度:\(O(nlogn+q)\)

複雜度?

using namespace std;
#include <bits/stdc++.h>
 
const int N = 2e5 + 5;
const int M = __lg(N) + 1;
int st[N][M];
 
signed main() {
    for(int i = 0; i < N; ++i) 
        for(int j = 0; j < M; ++j) 
            st[i][j] = 1e9;
    int n, q;
    cin >> n >> q;
    vector <int> a(n);
    for(auto &i : a) cin >> i;
    for(int i = 0; i < n; ++i) st[i][0] = a[i];
    for(int j = 1; j < M; ++j) 
        for(int i = 0; i + (1 << (j - 1)) < n; ++i) 
            st[i][j] = min(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
    while(q--) {
        int l, r;
        cin >> l >> r;
        --l, --r;
        int k = __lg(r - l + 1);
        cout << min(st[l][k], st[r - (1 << k) + 1][k]) << '\n';
    }
}

完整ㄉcode

題目

懶人標記

區間修改 單點查詢

Given an array of \(n\) integers, your task is to process \(q\) queries of the following types:

1. increase each value in range \([a,b]\) by \(u\).

2. what is the value at position \(k\) ?

怎麼做?

線段樹+懶標!

在線段樹每個節點的 struct 裡面都多開一格紀錄懶標的值

查詢的時候把經過的節點的懶標的值加起來!

區間修改

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

把 [3, 7] 都加上 3

[3, 4]

[1, 2]

[8, 8]

[7, 7]

[6, 6]

[5, 5]

[4, 4]

[3, 3]

[2, 2]

[1, 4]

[5, 8]

[7, 8]

[5, 6]

[1, 1]

[1, 8]

黑色是數值

黃色是懶標

3

[3, 7]

[3, 4]

[3, 4]

[5, 7]

[5, 6]

3

[7, 7]

[7, 7]

3

單點查詢

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

3

0

3

0

0

0

0

0

3

0

0

0

0

0

0

0

0

0

0

查詢6的值

[3, 4]

[1, 2]

[8, 8]

[7, 7]

[6, 6]

[5, 5]

[4, 4]

[3, 3]

[2, 2]

[1, 4]

[5, 8]

[7, 8]

[5, 6]

[1, 1]

[1, 8]

黑色是數值

黃色是懶標

ret = 3

ret = 3

ret = 3

ret = 0

題目

區間修改 區間查詢

There is an array of \(n\) elements, initially filled with zeros. You need to write a data structure that processes two types of queries:

1. add to the segment from \(l\) to \(r−1\) the number \(v\).
2. find the minimum on the segment from \(l\) to \(r−1\).

怎麼做?

線段樹+懶標!

跟上一個差不多

但是要在查詢的時候把懶標往下推

區間修改

0

0

0

0

0

0

0

0

0

0

4

0

3

0

3

0

4

0

3

0

7

0

4

0

4

0

3

0

6

0

7

0

8

0

4

0

7

0

8

0

把 [3, 7] 都加上 3

[3, 4]

[1, 2]

[8, 8]

[7, 7]

[6, 6]

[5, 5]

[4, 4]

[3, 3]

[2, 2]

[1, 4]

[5, 8]

[7, 8]

[5, 6]

[1, 1]

[1, 8]

黑色是數值

黃色是懶標

3

[3, 7]

[3, 4]

[3, 4]

[5, 7]

[5, 6]

3

[7, 7]

[7, 7]

3

其實跟剛剛一樣 la

區間查詢

0

0

0

0

0

0

0

0

0

0

4

0

3

0

3

0

4

0

3

0

7

3

4

3

4

0

3

0

6

3

7

0

8

0

4

0

7

0

8

0

查詢[4, 6]的值

[3, 4]

[1, 2]

[8, 8]

[7, 7]

[6, 6]

[5, 5]

[4, 4]

[3, 3]

[2, 2]

[1, 4]

[5, 8]

[7, 8]

[5, 6]

[1, 1]

[1, 8]

黑色是數值

黃色是懶標

[4, 6]

[4, 6]

[5, 6]

[4, 4]

[4, 4]

[4, 4]

0

3

3

7

[5, 6]

ans = 3 + 4 = 7

ans += 3 + 7 = 10

ans = 7

ans = 10

ans = 7

ans = 7

一些注意事項

  • 要記得你的懶標是「懶標+節點的值是正確的值」還是「節點的值就是正確的值」
  • 記得推懶標
  • 如果有很多種修改要記得這些修改的先後順序
  • 複雜度依舊是 \(O(nlogn)\)

code

#include <bits/stdc++.h>
using namespace std;

struct Node{
    int v = 0, tag = 0;
    int rv() {
        return v + tag;
    }
};
vector <Node> st;
vector <int> a;
void push(int x) {
    st[2 * x].tag += st[x].tag;
    st[2 * x + 1].tag += st[x].tag;
    st[x].v = st[x].rv();
    st[x].tag = 0;
}
void build(int l, int r, int x) {
    if(l == r) {
        st[x].v = a[l];
        return;
    }
    build(l, mid, 2 * x);
    build(l, mid, 2 * x + 1);
    st[x].v = min(st[2 * x].rv(), st[2 * x + 1].rv())
}
void modify(int ql, int qr, int v, int l, int r, int x) {
    if(l == ql && r == qr) {
        st[x].tag += v;
        return;
    }
    push(x);
    if(qr <= mid) modify(ql, qr, v, l, mid, 2 * x);
    else if(mid < ql) modify(ql, qr, v, mid + 1, r, 2 * x + 1);
    else {
        modify(ql, mid, v, l, mid, 2 * x);
        modify(mid + 1, qr, v, mid + 1, r, 2 * x + 1);
    }
    st[x].v = min(st[2 * x].rv(), st[2 * x + 1].rv());
}
int query(int ql, int qr, int l, int r, int x) {
    if(l == ql && r == qr) return st[x].rv();
    push(x);
    if(qr <= mid) return query(ql, qr, l, mid, 2 * x);
    else if(mid < ql) return query(ql, qr, mid + 1, r, 2 * x + 1);
    else return min(query(ql, mid, l, mid, 2 * x), query(mid + 1, qr, mid + 1, r, 2 * x + 1)); 
}
signed main() {
    int n, m;
    cin >> n >> m;
    st.resize(4 * n + 1);
    a.resize(n + 1);
    for(int i = 1; i <= n; ++i) cin >> a[i];
    build(1, n, 1);
    while(m--) {
        int op, ql, qr, v;
        cin >> op;
        if(op == 1) {
            cin >> ql >> qr >> v;
            modify(ql + 1, qr, v, 1, n, 1);
        }
        else {
            cin >> ql >> qr;
            cout << query(ql + 1, qr, 1, n, 1) << '\n';
        }
    }
    return 0;
}

緹牧

Made with Slides.com