楊宗儒@Sprout 2023
2023年的講義 = 2021年的講義 + 2022年的講義
費波那契講義
#include<algorithm>
函數名稱
對應的參數型別/回傳型別
函數的說明
關於這個函數的範例 code
一些技術細節
這堂課絕對無法教完所有C++的 library function。請養成自己查資料的能力
給你一堆數字N_i,輸出這些數字從小排到大的結果
std::sort(begin, end)
也就是在容器的 [begin, end) 的範圍內由小排到大
註: 左閉右開是 STL function 常見的規格
複雜度:O(nlogn)
std::sort 使用了混合了 quicksort 和 O(n^2) 的排序演算法
Array
#include<algorithm>
#include<iostream>
int main() {
int arr[] = {4, 8, 7, 6, 3};
int arrsize = 5;
sort(arr, arr + arrsize);
// arr + size 是最後一個元素的「下個位置」的指標
for(int i = 0; i < arrsize; i++) {
cout << arr[i] << " ";
}
cout << endl;
// output: 3 4 6 7 8
}
Vector
#include<algorithm>
#include<iostream>
#include<vector>
int main() {
vector<int> v({4, 8, 7, 6, 3});
sort(v.begin(), v.end());
// v.end() 是 v 的最後一個元素的「下個位置」的iterator
for(int i = 0; i < arrsize; i++) {
cout << v[i] << " ";
}
cout << endl;
// output: 3 4 6 7 8
}
給你一堆數字N_i,輸出這些數字從大排到小的結果
給你一坨學生S_i,輸出這些學生照各科成績和排序的結果
.
.
.
不只是從小排到大
std::sort(begin, end, cmp)
cmp: 定義「 a 小於 b 」的函數
由大排到小
bool cmp(int a, int b) {
return a > b;
}
不是 int
bool cmp(Student a, Student b) {
return (a.eng + a.math) < (b.eng + b.math);
}
struct Student{
int math, eng;
};
補充:Lambda function
當有很多排序的方式 cmp1, cmp2, ...
定義在外面顯得很沒效率
sort(v.begin(), v.end(), [](int a, int b) {
return a > b;
});
vector<int> v;
用法:[](參數){函數內容} 即可代表ㄧ個函數
補充:cmp小小眉角
cmp 必須滿足以下性質,否則有機會吃RE:
補充:operator overloading
struct S {
int math, eng;
}
bool operator < (S a, S b) {
return a.math + a.eng < b.math + b.eng;
}
int main() {
vector<S> v(4);
sort(v.begin(), v.end());
}
find(begin, end, element);
如果今天容器的元素已經排序好了,一次查詢可以砍掉一半的可能
std::binary_search(begin, end, element);
std::lower_bound(begin, end, element);
std::upper_bound(begin, end, element);
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main() {
vector<int> v({9, 8, 7, 6, 5, 5, 4, 3, 2, 1})
sort(v.begin(), v.end());
cout << *(upper_bound(v.begin(), v.end(), 5)) << endl; // 6
cout << *(lower_bound(v.begin(), v.end(), 5)) << endl; // 5
cout << (find(v.begin(), v.end(), 13)) == v.end() << endl; // 1
}
std::nth_element(begin, m, end);
註:這個函數的複雜度分析很有趣,推薦有興趣的同學去研究
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main() {
vector<int> v({9, 8, 7, 6, 5, 5, 4, 3, 2, 1})
int rank = 4;
nth_element(v.begin(), v.begin() + rank, v.end());
cout << v[4] << endl; // 4
}
*(begin + 0) = start
*(begin + 1) = start + 1
.
.
*(begin + i) = start + i
複雜度:O(n)
iota(begin, end, start);
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main() {
vector<int> v(5);
iota(v.begin(), v.end(), 4);
for(int i = 0; i < 5; i++) {
cout << v[i] << " ";
}
cout << endl;
// 4, 5, 6, 7, 8
}
reverse(begin, end);
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main() {
vector<int> v({9, 8, 7, 6, 5, 4, 3, 2, 1})
reverse(v.begin(), v.end());
for(int i = 0; i < 9; i++) {
cout << v[i] << " ";
}
cout << endl;
// 1, 2, 3, 4, 5, 6, 7, 8, 9
}
next_permutation(begin, end);
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main() {
vector<int> v(3);
iota(v.begin(), v.end(), 1);
do {
for (auto i : v)
cout << i << " ";
cout << "\n";
} while (next_permutation(v.begin(), v.end()));
/*
output:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
*/
}
find(begin, end, element);
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main() {
vector<int> v({3, 2, 1});
do {
for (auto i : v)
cout << i << " ";
cout << "\n";
} while (prev_permutation(v.begin(), v.end()));
/*
output:
3 2 1
3 1 2
2 3 1
2 1 3
1 3 2
1 2 3
*/
}
find(begin, end, element);
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main() {
vector<int> v(5);
fill(v.begin(), v.end(), 48763);
for (auto i : v)
cout << i << " ";
cout << "\n";
// output: 48763, 48763, 48763, 48763, 48763
}
random_shuffle(begin, end);
accumulate(begin, end, start);
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main() {
vector<int> v({4, 8, 7, 6, 3})
cout << accumulate(v.begin(), v.end(), 1) << endl;
// output: 29(4 + 8 + 7 + 6 + 3 + start=1)
}
unique(begin, end);
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main() {
vector<int> v({1, 2, 3, 2, 3, 1});
sort(v.begin(), v.end());
auto tmp = unique(v.begin(), v.end()); // v.begin() + 3
v.resize(tmp - v.begin()); // aka v.resize(3)
for (auto i : v)
cout << i << " ";
cout << "\n";
// output: 1, 2, 3
}
OJ #153
OJ #4949
題目:
給定一個排序好且元素相異的字串,依照字典序輸出所有排列
題目:
給定一個排序好且元素相異的字串,依照字典序輸出所有排列
std::next_permutation()
next_permutation() == false
do {
輸出字串
枚舉下一個排列
} while(還沒枚舉完);
題目:
給定大小為 N 的陣列 a, Q 筆詢問。每筆詢問會有
L, R,輸出滿足 L ≤ a_i ≤ R 的 a_i 的數量。
先不管 STL,你會怎麼做?
對於每筆詢問,我們都掃描一次陣列去數滿足L ≤ a_i ≤ R 的 a_i 的數量。
用兩層迴圈即可解決
while(還有詢問) {
for i in v {
if 滿足條件 {
數量+1
}
}
輸出數量
}
複雜度:O(NQ)
N, Q ≤ 10^6
=> TLE for subtask 2
觀察:
如果先把陣列排序好
滿足條件的數量: 1 + 最後一個滿足的元素位置 - 第一個滿足的元素的位置
1 + 最後一個滿足的位置 : 大於 R 的第一個元素
第一個滿足的位置: 大於等於 L 的第一個元素
觀察:
如果先把陣列排序好
滿足條件的數量: 1 + 最後一個滿足的元素位置 - 第一個滿足的元素的位置
1 + 最後一個滿足的位置 : 大於 R 的第一個元素
第一個滿足的位置: 大於等於 L 的第一個元素
lower_bound() /upper_bound()
把陣列排序
while(還有詢問) {
a = 找到大於 R 的第一個位置
b = 找到大於等於 L 的第一個位置
輸出 a - b
}
複雜度:O(QlogN)
因為 lower_bound/upper_bound 使用二分搜尋法
還有一些建議練習題
OJ # 804
OJ #448
OJ #1111
OJ #2024