其實隨時都可以用
給你一個長度\(n\)的序列,求出有多少對\(i \leq j\),使得\(a_i + ... + a_j = j - i + 1\)。
\(n \leq 10^5\)
給你一個長度\(n\)的序列,求出最少需要插入幾個數字(任意數值)才能使序列中沒有區間和為\(0\)的子區間。
\(n \leq 2*10^5, a_i \neq 0\)
給你一個長度\(n\)的序列,有\(m\)筆操作,每次選定一個區間,將奇數項(由該區間開始編號)加上\(x\)和將偶數項扣掉\(x\),問最後序列的長相
\(n, m \leq 10^6\)
Segment Tree
好像沒有什麼好方法維護?
好像沒有什麼好方法維護?(其實有啦w
假設我們要找\([2, 6]\)的話...
陣列型(最好寫、空間小)
指標型(算好寫、可以處理持久化和動態開點、空間肥)
偽指標型(只有 \(Wiwihorz\)會寫的毒瘤)
#include <iostream>
#include <algorithm>
#define ll long long
#define maxn 100005
using namespace std;
int seg[4 * maxn];
int main() {
}
根節點編號\(1\),對大小為\(n\)的序列需要\(4 \times hbit(n)\)個節點位置
每次往下走一層,複雜度\(O(logn)\)
0. 如果該節點區間為空,直接 return 掉
int seg[4 * maxn];
void modify(int cur, int l, int r, int ind, int val) {
if (r <= l) return;
if (r - l == 1 && l == ind) {
seg[cur] += val;
return;
}
int mid = (l + r) / 2;
if (ind < mid) modify(cur * 2, l, mid, ind, val);
else modify(cur * 2 + 1, mid, r, ind, val);
seg[cur] = seg[cur * 2] + seg[cur * 2 + 1];
}
0. 如果該節點區間為空或是不包含詢問區間,直接 return 掉
int query(int cur, int l, int r, int ql, int qr) {
if (r <= l || ql >= r || qr <= l) return 0;
if (ql <= l && qr >= r) {
return seg[cur];
}
int mid = (l + r) / 2;
return query(cur * 2, l, mid, ql, qr) + \
query(cur * 2 + 1, mid, r, ql, qr);
}
度
Basic Lazy Tag on Segment Tree
總不能分成一堆單點加值來弄吧?
我懶
對每個節點多紀錄一個「懶惰標記 Lazy tag」,代表目前加到那裡的答案。
在修改時只動到修改的區間,查詢時順帶資訊
(\(seg[cur]\)代表當前節點經過子節點修改後的答案
該節點的懶標對\(seg[cur]\)做事,遇到的時候再處理。
這份講義會先討論第一種
跟區間查詢的程式碼類似
0. 如果該節點區間為空或是不包含修改區間,直接 return 掉
3
3
3
[1, 2)
[2, 3)
[3, 5)
6
3
6
12
操作:
把[1, 5)的每一項東西加上 3
剛剛的懶標\(lazy[cur]\)紀錄的是「這個區間的每個東西被加到多少」
\(seg[cur]\)在更新時要記得把子節點的長度考慮進去
#include <iostream>
#include <algorithm>
#define maxn 100005
using namespace std;
int seg[4 * maxn], lazy[4 * maxn];
void modify(int cur, int l, int r, int ql, int qr, int val) {
if (r <= l || ql >= r || qr <= l) return;
if (ql <= l && qr >= r) {
lazy[cur] += val;
return;
}
int mid = (l + r) / 2;
modify(cur * 2, l, mid, ql, qr, val);
modify(cur * 2 + 1, mid, r, ql, qr, val);
seg[cur] = seg[cur * 2] + (mid - l) * lazy[cur * 2] + \
seg[cur * 2 + 1] + (r - mid) * lazy[cur * 2 + 1];
}
0. 如果該節點區間為空或是不包含修改區間,直接 return 掉
0 + 3
3
3
[3, 4)
[2, 3)
[3, 5)
6
3
6
12
操作:
查詢[2, 4)的
總和
+3
int query(int cur, int l, int r, int ql, int qr) {
if (r <= l || ql >= r || qr <= l) return 0;
if (ql <= l && qr >= r) {
return seg[cur] + (r - l) * lazy[cur];
}
int mid = (l + r) / 2;
return query(cur * 2, l, mid, ql, qr) + query(cur * 2 + 1, mid, r, ql, qr) \
+ (min(qr, r) - max(ql, l)) * lazy[cur];
}
\(n \leq 10^5, 矩形範圍 \leq 10^6\)
Sparse Table
\(n \leq 10^5, q \leq 2 * 10^6\)
用線段樹做?\(O((n + q)logn)\)
2 | 3 | 4 | 2 | 3 | 6 | 1 | 3 |
---|
2 | 3 | 2 | 2 | 3 | 1 | 1 | x |
---|
2 | 2 | 2 | 1 | 1 | x | x | x |
---|
1 | x | x | x | x | x | x | x |
---|
從小的\(i\)開始做,\(i = 0\)時,\(sp[i][j] = a[j]\)
對於 \([j, j + 2^i - 1]\)的最小值,可以將它拆成$$min([j, j + 2^{i - 1} - 1], [j + 2^{i - 1}, j + 2^i - 1])$$
記得 \(j\) 只能跑到 \(n - 2^i\)
#include <iostream>
#include <algorithm>
#define maxn 100005
using namespace std;
int sp[18][maxn], a[maxn];
int n;
int main() {
for (int i = 0;i < 18;i++) {
for (int j = 0;j < n - (1<<i) + 1;j++) {
if (i == 0) sp[i][j] = a[j];
else sp[i][j] = min(sp[i - 1][j], \
sp[i - 1][j + (1<<(i - 1))]);
}
}
}
Sparse Table 最強的是在當支援的函式
\(f\) 可以有\(f(i, i) = f(i)\)。
1 | 2 | 4 | 2 | 5 | 3 | 7 | 2 |
---|
選定一個\(x\),使得兩個區間聯集會是整個區間