不知道是基礎還是進階資料結構(二)
今天的內容
- 有懶標的線段樹
- 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;
}
緹牧
資料結構2
By mouyilai
資料結構2
- 140