## 概念複習
1. 結構 Struct
2. 指標 Pointer
3. 動態記憶體配置
## 結構 Struct
通常是拿來自定義資料 type,比如醫院要記錄病人的姓名、年紀、性別、健保卡號、看病日期等等
這時候用 struct 將這些資料的 type 整合到一起就會變得很方便,以下是範例
```cpp=
struct point {
int x, y ;
}
point p[100] ;
p[0].x = p[0].y = 1 ;
```
如果想要直接在 struct 內部初始化元素,有兩種做法
```cpp=
struct point {
point* next = NULL ;
int x = 0, y = 0 ;
}
```
```cpp=
struct point {
point* next ;
int x, y ;
point() : next(NULL), x(0), y(0) ;
// point(int xn, int yn, point* nxtp = NULL)
// : next(nxtp), x(xn), y(yn)
}
// point(a, b) ; -> x = a, y = b, next = NULL
```
## 指標 Pointer
指標也是一種變數,但是並非用於儲存常見 type 的值,而是"儲存那些值的記憶體空間"的地址
以下是指標的基礎用法
```cpp=
int a = 10 ;
int* p = &a ;
cout << "the value of a is " << *p << '\n' ;
int arr[3] = {1, 2, 3} ;
int* pa = arr ;
for (int i=0;i<3;i++)
cout << "arr[" << i << "] is " << *(pa+i) ;
```
當然最常用的情況還是在 function 傳遞參數,用指標才能修改不同 function 的變數
```cpp=
void swap_ab(int* a, int* b) {
int tmp = *a ;
*a = *b ;
*b = tmp ;
return ;
}
```
不過以上這個情況在 C++ 中可以利用傳參的方式
```cpp=
void swap_ab(int &a, int &b) {
int tmp = a ;
a = b ;
b = tmp ;
return ;
}
```
最後雙重指標有可能用在二維指標,但是在 C++ 的情況下不常用就不贅述了
## 動態記憶體配置
動態記憶體配置適合用在需要新增、修改、刪除等操作的變數
```cpp=
int *p1 = new int ;
int *p2 = new int(7) ;
*p1 = 10 ;
cout << *p1 << ' ' << *p2 << '\n' ;
delete p1 ;
delete p2 ;
```
當然不是只有變數可以用,也可以用在陣列這種資料結構上
```cpp=
int *pa1 = new int[3] ; // size 3
int *pa2 = new int[3]{1, 2, 3} ;
...
delete[] pa1 ;
delete[] pa2 ;
```
甚至在二維陣列上也可以
```cpp=
int main() {
int **pta = new int*[4] ;
for (int i=0;i<3;i++)
pta[i] = new int[4]{0} ; // array [4][4]
pta[0][1] = 1 ;
pta[1][1] = 2 ;
pta[2][1] = 3 ;
...
for (int i=0;i<4;i++)
delete[] pta[i] ;
delete[] pta ;
return 0 ;
}
```
但最常用的還是搭配 struct 去做使用,通常是在 Linked List 這種動態資料結構上面
```cpp=
struct point {
int x, y ;
point* next ;
};
point *p1 = new point ;
point *p2 = new point ;
p1->x = 1, p1->y = 2 ;
p1->next = p2 ;
p2->x = 3, p2->y = 4 ;
p2->next = NULL ;
cout << p1->x << ' ' << p1->y << '\n' ;
cout << (*p2).x << ' ' << p1->next->y << '\n' ;
delete p1 ;
delete p2 ;
```
當然動態記憶體使用完記得都要用 `delete` 刪除,不然可能會出現問題
---
接下來都是之前沒提過的概念,主要的目的是作為幫助理解之後篇章與一些前置知識
## 雙指針
在同一個資料結構上用兩個指針(指標)進行操作,這兩個指針可以在同個位置,也可以在不同位置
兩個指針可以同向(同時往右或往左),也可以不同向(兩個靠近或遠離),也可以獨立運動
獨立運動就是某一指針移動不會影響另一指針,所以當一個指針移動,另外一個可以不動或移動
常見的雙指針可能會拿來比較兩指針對應的值,維護一個特定區間,以下給一些常見的應用
1. Sliding window: 維持一個動態區間,使區間可以滿足特定條件,兩個指針同向
2. 兩指針靠近+判斷: 迴文、排序後陣列找兩值相加為特定值
3. 快慢指針: 一個指針移動較快,另一個指針移動較慢
4. 折半指針: 將資料結構或問題拆成兩半,一個指針負責一半,各自計算再合併結果
5. Shrinking interval: 維護一個區間`[l, r]`,然後根據條件縮小範圍直到找到答案
6. 多序列雙指針: 兩個以上的已排序陣列用指針遍歷,通常用在合併排序上面
基本上目前只要知道前兩個,其餘的目前也不是很重要,但是是有使用到雙指針的一種實作
## 單調性
資料和函數值在某個範圍內保持遞增或遞減的性質就稱為"單調性",用數學的語言來說
1. 單調遞增: 對於任意 $i<j$,必定 $f(i) \le f(j)$、或 `A[i] <= A[j]`
2. 單調遞減: 對於任意 $i<j$,必定 $f(i) \ge f(j)$、或 `A[i] >= A[j]`
所謂的單調性,不一定是題目給定的結構有單調性,也可以主動去做一個具有單調性的結構
不過這單元不會講太多程式碼,因為只是介紹一下單調性而已
單調性最主要的功能就是讓資料變得可預期,有了可預期的資料後就能更容易做優化
比如把問題拆成大於 $X$、不大於 $X$ 兩個部分,再去做處理,最典型的例子就是二分搜
每次處理都會縮減一半的範圍,就是基於單調性在這個問題的應用
以下列出四個比較常見的應用
1. 二分搜
2. 雙指針、Sliding window
3. 單調資料結構
4. 單調 DP
拿點一來說明,假設要找的數字為 $A$,二分搜過程中找到的數字為 $X_1, X_2, ...$
那當我們確定 $A < X_1$,我們就可以確定"如果 $A$ 在資料結構中,位置一定在 $X_1$ 前方"
所以就可以縮減範圍,另一個情況 $A > X_1$ 也是同理,一樣可以縮減範圍
## 快速冪
快速計算 $x^a$ 的方法,這裡的 $x, a$ 都是整數,題目通常會要求結果做 (mod P)
如果用暴力的解法,一個個慢慢乘那效率就會很低,這時候就可以用到指數的特性
* $(a^b) \times (a^b) = a^{2b}、(a^b)\times(a) = a^{b+1}$
有這個特性我們就能高效的處理這個問題,舉個例子,假設我們想求 $2^{25}$,進行拆分的步驟如下:
1. $2^{25} = 2^{24} \times 2$
2. $2^{24} = 2^{12} \times 2^{12}$
3. $2^{12} = 2^{6} \times 2^{6}$
4. $2^{6} = 2^{3} \times 2^{3}$
5. $2^{3} = 2^{2} \times 2$
6. $2^{2} = 2^1 \times 2^1$
其實就是遇到偶數拆成兩個相乘,遇到奇數就拆成一個和 $a$ 相乘
```cpp=
// P = 1000000009
int rec(int a, int b) {
if (b == 1) return a ;
if (b & 1) return (rec(a, b-1) * a) % P ;
// else: even
int tmp = rec(a, b/2) % P ;
return (tmp * tmp) % P ;
}
```
還有一個跟快速冪相關的應用-快速計算費氏數列
$\left[
\begin{array}{ccc}
f(n) \\
f(n-1) \\
\end{array}
\right] =
\left[
\begin{array}{ccc}
1 & 1 \\
1 & 0 \\
\end{array}
\right] \times
\left[
\begin{array}{ccc}
f(n-1) \\
f(n-2) \\
\end{array}
\right],
\left[
\begin{array}{ccc}
f(1) \\
f(0) \\
\end{array}
\right] =
\left[
\begin{array}{ccc}
1 \\
0 \\
\end{array}
\right]$
由上方公式可以推導出以下公式
$\left[
\begin{array}{ccc}
f(n) \\
f(n-1) \\
\end{array}
\right] =
\left[
\begin{array}{ccc}
1 & 1 \\
1 & 0 \\
\end{array}
\right]^{n-1} \times
\left[
\begin{array}{ccc}
1 \\
0 \\
\end{array}
\right]$
對矩陣做快速冪後就可以很快算出費氏數列 `f(n) = (A^{n-1})[0][0]`
## 預處理
在做主要計算之前,先對資料作處理,讓後續的計算更方便或是更有效
舉個例子,搜尋前先將資料排序好,就能用二分搜,這裡的排序就是預處理,以下是常見的預處理
1. 前綴和、前綴 XOR、差分
2. 建表(比如質數表、最短路徑表、LCA 表、DP 狀態表)
3. Rolling Hash
4. Sparse Table
基本上預處理就是在用一定的時間、空間去換取後方主要計算的效率
所以在預處理的時間超過後方主要計算的情況下,不如放棄預處理直接開始主要計算