# 宜宸 AP325 筆記
- 第 0 章
---
### 第 0 章
(1) I/O (輸入輸出) 速度 => cin/cout 比 scanf/printf 慢很多 (TLE)
```c++=
#include<bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
// code here
}
```
背起來,放開頭,用了之後不能使用 `scanf`, `printf`。
(2)複雜度估算 = O(執行次數) -> 念 bigO,n 代表你輸入幾個數 (資料量)
複雜度就是在估算你的「程式執行時間」。
常見的複雜度 :
- $O(n)$
- $O(nlogn)$
- $O(n^2)$
- $O(2^n)$
原則 :
1. 不算常數,ex : O(5n) = O(3n) = O(n)
2. 兩個函數相加的 bigO 取較大的那個,$O(n^2)+O(n)=O(n^2)$
3. f(n) = O(n),但你執行了 m 次 f(n),複雜度就會是 $O(nm)$
4. 一般來說 bigO (複雜度) 是很悲觀的,都是認為 n 是趨於無限大的數,也就是估計一個 worst case。
---
### 第 2 章
#### 排序
- 由小排到大,由大排到小
- 二分搜尋法
- 「特定條件」來進行排序
- struct
```c++=
#include<bits/stdc++.h>
using namespace std;
// 建立一個資料型態叫 student
struct student {
string name;
string id;
int grade;
};
int main() {
student p[26];
for(int i=0;i<26;i++) {
cin >> p[i].name >> p[i].id >> p[i].grade;
}
}
```
```c++=
#include<bits/stdc++.h>
using namespace std;
struct point {
int x;
int y;
};
int main() {
int n;
cin >> n;
point p[n];
}
```
- sort
- `sort(start, end+1, compare);`
- 預設由小排到大,如果想由大排到小就要寫 compare function (`cmp`)。
- 若是 struct 類型想要排序,一定要傳入 compare function,才能知道這個 struct 想要怎麼排。
```c++=
#include<iostream>
#include<algorithm>
using namespace std;
int main() {
int n;
cin >> n;
int a[n];
for(int i=0;i<n;i++) {
cin >> a[i];
}
sort(a, a+n);
}
```
```c++=
#include<bits/stdc++.h>
using namespace std;
bool cmp(int x, int y) {
return x > y;
}
int main() {
int n;
cin >> n;
int a[n];
for(int i=0;i<n;i++) cin >> a[i];
sort(a, a+n, cmp);
for(int i=0;i<n;i++) cout << a[i] << " ";
cout << "\n";
return 0;
}
```
```c++=
#include<bits/stdc++.h>
using namespace std;
struct point {
int x;
int y;
};
// 傳址
bool cmp(point &s, point &t) {
return s.y > t.y;
}
int main() {
int n;
cin >> n;
point p[n]; // 共有 n 個點,每個點都有 p[i].x, p[i].y;
for(int i=0;i<n;i++) {
cin >> p[i].x >> p[i].y;
}
sort(p, p+n, cmp); // struct 需要 cmp 函式才知道自己該怎麼做排序
for(int i=0;i<n;i++) {
cout << p[i].x << " " << p[i].y << "\n";
}
return 0;
}
```
- 如果是「字串」進行排序,則按照「字典序」去做排序。
```c++=
#include<bits/stdc++.h>
using namespace std;
bool cmp(string x, string y) {
return x > y;
}
int main() {
string s[3];
for(int i=0;i<3;i++) cin >> s[i];
sort(s, s+3, cmp);
for(int i=0;i<3;i++) cout << s[i] << "\n";
return 0;
}
```
#### STL
(1) `pair<Type, Type> variable_name;`
- pair 有兩個元素 `first`, `second`。
- `<>` 裡面指的是 pair first 和 second 的 type。
- ex : `pair<int, int> p;`
```c++=
#include<bits/stdc++.h>
using namespace std;
/*
struct pair {
Type first;
Type second;
};
*/
bool cmp(pair<int, int> &s, pair<int, int> &t) {
return s.second > t.second;
}
int main() {
int n;
cin >> n;
pair<int, int> point[n];
for(int i=0;i<n;i++) {
cin >> point[i].first >> point[i].second;
}
sort(point, point+n, cmp);
for(int i=0;i<n;i++) {
cout << point[i].first << " " << point[i].second << "\n";
}
return 0;
}
```
(2) `vector<Type> variable_name;`
- 一個擁有動態大小的陣列。
- ex : `vector<int> vec;`
- `vec.push_back(n)` : 把 n 塞進 vec 這個 vector 裡面。
- `vec.size()` : 回傳 vec 裡面有幾個元素。
- 其他就跟陣列操作一樣 ex: `vec[i]`
```c++=
#include<bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
vector<int> vec;
int tmp;
for(int i=0;i<n;i++) {
cin >> tmp;
vec.push_back(tmp);
}
// ...
sort(vec.begin(), vec.end());
for(int i=0;i<vec.size();i++) {
cout << vec[i] << " ";
}
int sum = 0;
for(int i=0;i<vec.size();i++) {
sum += vec[i];
}
}
```
(3) `set<Type> variable_name;`
- 集合裡面沒有重複的數。
- `set<int> st;`
- `st.insert(n);` -> 把 n 丟進去集合裡面。
- 遍歷 (排序好的)
```c++=
set<int> st;
for(auto it=st.begin();it != st.end(); it++) {
cout << *it << " ";
}
```
- `st.clear()` -> 直接把集合清空。
- `st.erase(n)` -> 把 n 從集合裡面移除。
- `st.find(n)` -> 從集合裡面找到 n 並回傳。
- `st.size()` -> 集合裡面目前共有多少數。
- `st.count(n)` -> 集合裡 n 總共有幾個,在 set 中通常只有 0 跟 1,multiset 除外。
(4) `map<Key_Type, Value_Type> variable_name;`
- `map<string, int> mp;`
- `mp["kevin"] = 1;`
---
#### 二分搜尋
- 前提 : 二分搜尋只適用在「已**排序好**的陣列當中」。
- 名詞 :
- 左界 (left-side) : 搜尋標竿陣列左方的 index。 (小)
- 右界 (right-side) : 搜尋標竿陣列右方的 index。 (大)
- mid : 目前搜尋到的數字
```
index : 0 1 2 3 4
value : 11 22 44 67 121
```
```c++=
#include<bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
int a[n];
for(int i=0;i<n;i++) cin >> a[i];
int target;
cin >> target;
// 1. check 陣列是否排序好
// 2. 初始化左界 / 右界
int left = 0, right = n - 1;
while(left <= right) {
int mid = (left + right) / 2;
if(a[mid] < target) {
left = mid + 1;
}
else if(a[mid] > target) {
right = mid - 1;
}
else {
cout << target << "\n";
break;
}
}
return 0;
}
```
- `lower_bound()` : 用來找到第一個大於等於某個值的位置。
- `lower_bound(first, last, target);`
- 回傳「指標」(位置)
```c++=
#include<bits/stdc++.h>
using namespace std;
int main() {
int a[10] = {1, 6, 13, 22, 23, 24, 51, 67, 99, 134};
// int index = lower_bound(a, a+10, 23); 錯誤示範,不能用整數去接位置
// auto 在 C++ 中可以自動識別回傳類型
auto it = lower_bound(a, a+10, 23); // 0x7ffc6f55d770
// 使用 * 可以把該位置的值給取出來
cout << *it << "\n"; // 23
// 如果想知道 index
cout << it - a << "\n"; // 4
return 0;
}
```
- `upper_bound()` : 找到第一個大於某值的位置。
- `binary_search()` : 只會回傳是否有找到。
```c++=
#include<bits/stdc++.h>
using namespace std;
int main() {
int a[10] = {1, 6, 13, 22, 23, 24, 51, 67, 99, 134};
// binary_search 函數會回傳 bool
// 1 -> true
// 0 -> false
bool isExisted = binary_search(a, a+10, 1000);
cout << isExisted << "\n";
return 0;
}
```
---
#### 快速冪
- 快速冪是如何快速計算 $x^y$ 的方法。
- 概念 : `(x * y) % p = (x % p) * (y % p)`
- 計算 $x^y$ 最直接的方法就是 $x$ 乘自己 $y$ 次,但要記得每次乘完都要取餘數 $(% p)$,不然算完的結果可能會 **overflow** (超過 `int` / `long long int` / ... 的範圍)。-- 方法一
```c++=
int t = x;
for(int i=0;i<y;i++) {
t = (t * x) % p;
}
```
- 方法一的效率太差 $O(n)$,如果 $y$ 是十億則迴圈要執行十億次,所以我們可以用「遞迴」來幫助我們把複雜度降到 $O(logn)$。
- 簡單來說就是 :
- 如果 $y$ 是奇數,則遞迴出 $y-1$ 次方的答案後再乘一次 $x$,意即 $f(y-1)*x$ (假設 f 是遞迴函數)。
- 如果 $y$ 是偶數,則求出 $y/2$ 次方後再自己乘自己,意即 $f(y/2)*f(y/2)$,終止條件是當 $y=0$ 時,回傳 $1$。

- 可以看例題 `P-2-3 快速冪`,範例 code :
```c++=
#include<bits/stdc++.h>
using namespace std;
long long exp(long long x, long long y, long long p) {
if (y == 0) {
return 1;
}
// 奇數
if (y % 2 == 1) {
return (exp(x, y-1, p) * x) % p;
}
// 偶數
long long t = exp(x, y/2, p);
return (t * t) % p;
}
int main() {
// x^y % p
long long x, y, p;
cin >> x >> y >> p;
long long ans = exp(x, y, p);
cout << ans << "\n";
}
```
---
### 第 3 章
#### 佇列 (queue)
- 宣告 -> `queue<Type> queue_name`,`queue<int> Q`。
- 新增 -> `Q.push(data)`。
- 刪除 -> `Q.pop()`,把出口 (排第一個的東西排掉),只是一個動作。
- 查看出口成員 -> `Q.front()`,回傳排第一個的 data。
- 檢查是否為空 -> `Q.empty()`,回傳 bool (true / false)。
- 查詢目前成員數 -> `Q.size()`。
- 遍歷 (traverse)
```c++=
#include<bits/stdc++.h>
using namespace std;
int main() {
queue<int> Q;
Q.push(1);
Q.push(2);
Q.push(3);
vector<int> vec;
while(!Q.empty()) {
int tmp = Q.front();
vec.push_back(tmp);
Q.pop();
}
}
```
#### 推疊 (stack)
- 宣告 -> `stack<int> st`。
- push -> `st.push(data)`,ex: `st.push(1)`。
- pop -> `st.pop()`,只是一個動作。
- top -> `st.top()`,回傳最上方的 data (最後 push 的)。
- 檢查是否為空 -> `st.empty()`。
```c++=
#include<bits/stdc++.h>
using namespace std;
int main() {
stack<int> st;
st.push(1);
st.push(2);
st.push(3);
vector<int> vec;
while(!st.empty()) {
int tmp = st.top();
vec.push_back(tmp);
st.pop();
}
}
```