建國中學 游承曦
回傳型別 函式名稱(參數型別1 參數名稱1, ...){
// do something
回傳參數;
}
void uhhh(int a, int b){
a = a + b;
}
int main(){
int x, y;
cin >> x >> y;
uhhh(x, y);
cout << x << " " << y << endl;
}
int f(int n){
if(n == 1 || n == 2){
return 1;
}
else{
return f(n-1) + f(n-2);
}
}
aka 自己呼叫自己
void MergeSort(int l, int r){
if(l >= r) return;
// Divide
int m = (l + r) / 2;
MergeSort(l, m), MergeSort(m+1, r);
// Conquer
int i = l, j = m+1, k = l;
while(i <= m && j <= r){
if(arr[i] < arr[j]) buf[k++] = arr[i++];
else buf[k++] = arr[j++];
}
while(i <= m) buf[k++] = arr[i++];
while(j <= r) buf[k++] = arr[j++];
for(int p=l ; p<=r ; ++p) arr[p] = buf[p];
}
int MergeSort(int l, int r){
if(l >= r) return 0;
// Divide
int m = (l + r) / 2;
int cnt = MergeSort(l, m) + MergeSort(m+1, r);
// Conquer
int i = l, j = m+1, k = l;
while(i <= m && j <= r){
if(arr[i] <= arr[j]) buf[k++] = arr[i++];
else{
cnt += m - i + 1;
buf[k++] = arr[j++];
}
}
while(i <= m) buf[k++] = arr[i++];
while(j <= r) buf[k++] = arr[j++];
for(int p=l ; p<=r ; ++p) arr[p] = buf[p];
return cnt;
}
A
B
C
F
G
H
E
D
根節點
葉節點
父節點
子節點
A
B
C
E
D
F
G
1. 每個節點都只有\(\leq 2\)個子節點
2. 第\(k\)層最多有\(2^k\)個節點
3. 深度為\(k\)的二元樹最多有
\(2^{k+1}-1\)個節點
H
I
1
2
3
5
4
6
7
9
12
從根節點為\(1\)開始 (1-base)
對於一個節點編號 \(k\),
則左子節點編號 \(2k\),
右子節點編號 \(2k+1\)
於是就可以用一條陣列存一棵二元樹了!
(RMQ)
int mx = -2147483648;
for(int i=L ; i<=R ; i++) mx = max(mx, a[i]);
cout << mx << endl;
然後你就發現你 TLE (Time Limit Exceed) 了
仔細想想,如果查區間 \([1,10^5] \times 10^5\) 次
那需要 \(10^{10}\) 次運算欸
複雜度 \(\mathcal{O}(n^2)\) 超爛的
Segment Tree
6
6
4
5
6
3
4
\([1, 6]\)
\([1, 3]\)
\([4, 6]\)
\([1,2]\)
\([3]\)
\([4, 5]\)
\([6]\)
6
1
\([1]\)
\([2]\)
3
2
\([4]\)
\([5]\)
6
6
4
5
6
3
4
\([1, 6]\)
\([1, 3]\)
\([4, 6]\)
\([1,2]\)
\([3]\)
\([4, 5]\)
\([6]\)
6
1
\([1]\)
\([2]\)
3
2
\([4]\)
\([5]\)
6
6
4
3
3
6
5
6
線段樹是一棵二元樹,每個節點維護一個區間的值
兩個子節點維護的是把原本的區間切一半的左右兩邊
因為是二元樹,如果原序列有 \(N\) 項,則深度最大為 \(\log N\) 層
所以每次查詢的複雜度最差為 \(\mathcal{O}(\log N)\)
二元樹
Wiwihorz
struct Node{
Node *l, *r;
int max_val;
Node(int v):l(NULL), r(NULL), max_val(v){}
} *rt;
你會需要這些東西:D
但顯然我沒有要講它:DDD
還記得剛剛才說二元樹可以用一條陣列存嗎?
線段樹也可以!
因為編號的關係,
線段樹的陣列大小要開到原序列的\(4\)倍大
cur
cur*2
cur*2 + 1
#include <iostream>
using namespace std;
#define N 100005
int arr[N];
int sgt[N * 4];
int main(){
}
void build(int l, int r, int o){
if(l == r){
sgt[o] = arr[l];
return;
}
int m = (l + r) / 2;
build(l, m, o * 2);
build(m+1, r, o * 2 + 1);
sgt[o] = max(sgt[o*2], sgt[o*2+1]);
}
原序列\(N\)項構造出的線段樹會有\(2N-1\)個節點
因此建樹的複雜度就是 \(\mathcal{O}(N)\) !
區間查詢的部分
可能會有下面四種情況:
查詢的區間完整包覆節點的區間 \(ql \leq L \ and \ R \leq qr\)
查詢的右界在節點區間中間以左 \(qr \leq M\)
查詢的左界在節點區間中間以右 \(ql > M\)
查詢的區間橫跨左右子節點 \(L \leq ql \leq M < qr \leq R\)
若節點區間為 \([L, R]\),則區間中點為 \(M\)
int Query(int ql, int qr, int L, int R, int o){
if(ql <= L && R <= qr){
return sgt[o];
}
int m = (L + R) / 2;
if(qr <= m){
return Query(ql, qr, L, m, o * 2);
}
else if(ql >= m + 1){
return Query(ql, qr, m+1, R, o * 2 + 1);
}
else{
return max(Query(ql, m, L, m, o * 2),
Query(m+1, qr, m+1, R, o * 2 + 1));
}
}
求 \(a[i]\) 到 \(a[j]\) 的最大值
求 \(a[i]\) 到 \(a[j]\) 的最大值 \(-\) 最小值
最大值、最小值
最大公因數
區間和
矩陣
etc.
那就在線段樹上做修改ㄅ
21
12
9
5
7
5
4
\([1, 6]\)
\([1, 3]\)
\([4, 6]\)
\([1,2]\)
\([3]\)
\([4, 5]\)
\([6]\)
6
1
\([1]\)
\([2]\)
3
2
\([4]\)
\([5]\)
21
9
5
8
11
15
27
void Modify(int p, int v, int l, int r, int o){
if(l == r){
sgt[o] = v;
return;
}
int m = (l + r) / 2;
if(p <= m) Modify(p, v, l, m, o * 2);
else Modify(p, v, m+1, r, o * 2 + 1);
sgt[o] = sgt[o * 2] + sgt[o * 2 + 1];
}
21
12
9
5
7
5
4
\([1, 6]\)
\([1, 3]\)
\([4, 6]\)
\([1,2]\)
\([3]\)
\([4, 5]\)
\([6]\)
6
1
\([1]\)
\([2]\)
3
2
\([4]\)
\([5]\)
操作:
把 \([3,5]\) 項加上 \(3\)
8
3
11
15
30
15
7
4
\([1, 6]\)
\([1, 3]\)
\([4, 6]\)
\([1,2]\)
\([3]\)
\([4, 5]\)
\([6]\)
6
1
\([1]\)
\([2]\)
3
2
\([4]\)
\([5]\)
操作:
查詢 \([2,4]\) 項的和
8
3
11
15
30
15
30
15
15
7
8
11
6
7
4
\([1, 6]\)
\([1, 3]\)
\([4, 6]\)
\([1,2]\)
\([3]\)
\([4, 5]\)
\([6]\)
6
1
\([1]\)
\([2]\)
3
2
\([4]\)
\([5]\)
操作:
查詢 \([2,4]\) 項的和
8
5
11
15
30
15
30
15
15
7
8
11
6
6
5
把懶標下推
int sgt[N * 4]={};
int laz[N * 4]={};
void push(int l, int r, int o){
if(laz[o] == 0) return;
int m = (l + r) / 2;
sgt[o * 2] += (m - l + 1) * laz[o];
laz[o * 2] += laz[o];
sgt[o * 2 + 1] += (r - m) * laz[o];
laz[o * 2 + 1] += laz[o];
laz[o] = 0;
}
int sgt[N * 4]={};
int laz[N * 4]={};
void push(int, int, int);
void Modify(int ql, int qr, int x, int L, int R, int o){
if(ql <= L && R <= qr){
sgt[o] += (R - L + 1) * x;
laz[o] += x;
return;
}
push(l, r, o);
int m = (L + R) / 2;
if(qr <= m){
Modify(ql, qr, x, l, m, o * 2);
}
else if(ql > m){
Modify(ql, qr, x, m+1, r, o * 2 + 1);
}
else{
Modify(ql, m, x, l, m, o * 2);
Modify(m+1, qr, x, m+1, r, o * 2 + 1);
}
sgt[o] = sgt[o * 2] + sgt[o * 2 + 1];
}
int Query(int ql, int qr, int L, int R, int o){
if(ql <= L && R <= qr){
return sgt[o];
}
push(l, r, o);
int m = (L + R) / 2;
if(qr <= m){
return Query(ql, qr, L, m, o * 2);
}
else if(ql > m){
return Query(ql, qr, m+1, R, o * 2 + 1);
}
else{
return Query(ql, m, L, m, o * 2) +
Query(m+1, qr, m+1, R, o * 2 + 1);
}
}
把 \(a[i]\) 到 \(a[j]\) 加上 \(x\)
把 \(a[i]\) 到 \(a[j]\) 全部取開根號
Hint:區間和 \(=\) 前綴 \(-\) 前綴
\(1 \leq n \leq 10^5\)
\(1 \leq N \leq 10^5\)
\(0 \leq A_i, \ B_i, \ C_i \leq 10^5\)
你需要支援兩種操作:
把座標位於 \((P, Q)\) 的值改成 \(K\)
查詢以 \((P, Q)\)、\((U, V)\) 畫成的矩形中所有數字的最大公因數
\(1 \leq N, \ M \leq 10^9\)
\(0 \leq K \leq 10^{18}\)
: O
實在是有人覺得遞迴型態的線段樹常數太大(X
所以就發明了用迴圈就能跑的線段樹
還可以把陣列大小壓到 \(2N\),懶標大小壓到 \(N\)
毒瘤中的毒瘤ww
例如值域線段樹
沒有用到的節點就直接不開空間
等到用到時再開新節點,會省很多空間(X)
實作的部分就要用指標才行
有興趣的自己去查w