---
# System prepended metadata

title: 基礎概念

---

## 概念複習
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

基本上預處理就是在用一定的時間、空間去換取後方主要計算的效率
所以在預處理的時間超過後方主要計算的情況下，不如放棄預處理直接開始主要計算