程式競賽

(slide ver)

You should read this slide along w/ this docs:

講師介紹

我不是專業的競程玩家,我沒有特別練習。

 

  • YTP: 第二輪被幹掉 (高中)
  • APCS:  4 3 (高中)
  • CPE:  5/7
  • PUPC:  2024 2025 銀獎
  • ACM-ICPC:  某題首殺 (感謝看題隊友: 鮭魚)
  • TOPC: 100/200名左右 (上週六打的)

目前戰績 (按照時間排序):

程式競賽介紹

需要學習的技術

程式能力: C++

請乖乖使用C++,不需要太高深的語法技巧

(甚至有些競賽跟你說只有部分題能用Python解)

(CPE前三題那種隨便,都是水題)

程式能力

高速寫程式碼,同時要兼顧可維護性。

 

英文能力

題目敘述都是英文

演算法

基礎: 一些資料結構得會的,尤其是樹。
Greedy: Activity Selection、Interval Cover、Optimal Matching、Huffman Coding、scheduling

DP:

  背包: 01背包、完全背包、分組背包、各種背包

  subsets(bitmask)、LCS/LIS

圖: D/BFS、MST(也算是greedy)、Shortest-Path(Dijkstra/Bellman/Floyd/SPFA)、Topo/DAG、強連通分量(Tarjan)、Min Cut/Max Flow、LCA

可能是技巧?: Disjoint Set、雙指標、滑窗、rolling hash、PQ、位元枚舉、分治、cache

演算法

沒,亂講的

 

看需求,如果想要打得好就要碰一點

競賽制度

有分為 OI制 、 ICPC制

  • 每一題有部份分,共200分
  • 沒有罰時
  • 看不到排行榜
  • 每題只有對/錯
  • 有罰時,一次20分鐘
  • 可以邊打邊看榜
  • 可以帶筆記/文件參賽(CPE不行)

ICPC:

OI (偏資奧):

賽制

不同題數: 題數高的優先 (廢話)

同題數: 看總時間,越少的優先 (也是廢話)

來看看這位玩家的分數吧

賽制

這傢伙不行啊

賽制

第1題比賽開始57分鐘後解出
第2題比賽開始20分鐘後解出
第3題比賽開始51分鐘後解出
第4題比賽開始175分鐘後解出被罰時1次
第5題有提交2次,但沒解出來
第6題比賽開始134分鐘後解出
第7題沒有提交

57 + 20 + 51 + (175 + 20) + 134 = 457

分數: 5題,t: 457

競賽題目格式

提交作答結果

  • AC (Accepted)          

  • WA (Wrong Answer) 

  • RE (Runtime Error)

  • CE (Compile Error)

  • TLE (Time Limit Exceeded)

  • MLE (Memory Limit Exceeded)

程式正確
程式輸出有誤

程式跑到一半掛了

編譯錯誤

超時

記憶體用量超過限制

Online Judge

線上可以找到題目寫的地方

ZeroJudge

  • 高中入門常用

  • 中文

  • 很多以前競賽的題目

  • 有不少新手友善的題目

Online Judge

  • 30年歷史,很多競賽題

  • 很有名,很有名

  • 題目量很大,幾萬題來的

TIOJ

  • 屬於建中資訊社

  • 不少有趣的校內賽題目可以打

台灣競賽/檢定資訊

APCS

大學程式設計先修檢測

分為 觀念/實作 兩部分

只想過畢業門檻的人不用看

CPE

大學程式能力檢定

7題

題源: onlinejudge.org

CPE

大學程式能力檢定

前3題,一定會有一題是從1星題題選(49題) 選出來的,把那49題都學會,你每次都可以保底1題。

ICPC

國際大學生程式設計競賽

程式設計競賽的最高殿堂

參賽資格難獲得

台灣最高規格的程式競賽就是ICPC亞洲區賽

ICPC

  • 三人一組,很考驗分工
  • 有分洲區賽/Final
  • 競賽結束前30mins會封榜,同時間開榜+頒獎很有看點。

ICPC

賽場都有吃的,賽後有晚餐,

比賽的時候,可以去休息室拿/吃零食

 

每隊可以帶一個吉祥物
玩偶/棉被 應該都行

ICPC

洲區賽參加資格:

  1. NCPC (大專賽) 前40

  2. TOPC (線上賽) 前40
  3. PUPC (私立賽) 前10
  4. NCTU (科大賽) 前10
  5. CPE 前10隊(約30人?)

總之歡迎各位來跟我搶我們學校的參賽資格就是

比賽技巧

查榜大法

題目相關技巧

關於CPE前三題...

  1. 前三題都在送分
  2. 看到題目感覺有一點演算法的味道? 不可能! 直接暴力解!!!
  3. 同上,請你忘記進階演算法。
  4. 如果看不出解題思路,請自己加油。
  5. 如果有解題思路,但沒辦法變成程式,請自己加油。

看題技巧

可以先看看測資,

簡單題可能看得出一些端倪,甚至是直接看出題目要幹嘛。


難一點的,可能可以看得出是圖、動態規劃。

如果看到什麼要把結果 % 1000000007的

 

題目可能是:

  • 動態規劃
  • 排列組合
  • 大數運算

只想過畢業門檻的人不用看

如果看到什麼

  • 從A~B有多少種方法
  • 有多少個subset滿足條件

 

題目可能是:

  • 動態規劃
  • 推狀態轉移

只想過畢業門檻的人不用看

程式部分

bits/stdc++.h

有沒有人對於include標頭檔很頭痛的?

#include<iostream>
#include<cmath>
#include<algorithm>
#include<bitset>
#include<map>
#include<set>
#include<vector>
#include<iterator>
#include<deque>
#include<cstring>
#include<cctype>

總之,有這麼一個東西可以用

#include  

bits/

std 

c++

.h

這還用說嗎?

這個檔案在 bits/ folder內

代表 standard (跟stdio/stdlib同意思)

代表 c++ (廢話)

標頭檔

#include<bits/stdc++.h>

注意:

請不要在程式競賽以外的地方

像是你的專案,使用這個標頭。

using namespace std;

標準庫內很多東西,

函數、類別、變數都存在std命名空間內

std::cout << "Hello" << std::endl;
std::vector<int> v;

引入std命名空間。
因此使用時無需使用std::

#include<bits/stdc++.h>

using namespace std;

int main() {
    cout << "Hello" << endl;   // 不用寫 std::cout 、 std::endl
    vector<int> v;       // 不用寫 std::vector
}

宣告陣列的小技巧

像這一題,他說有N個人,1 <= N <= 30000

所以你可能會建立一個陣列: int arr[30000] = {};

但是你要存取1 ~ 30000,所以你會減1,像是: arr[id-1]

那你不如把陣列設成: int arr[30001] = {};

可以直接用 arr[id] 存取。

題目的輸入

char c = getchar(); // 讀一個字元,EOF -> -1

c = cin.get(); // 讀一個字元,EOF -> -1

cin >> c; // 讀一個非空白字元,EOF -> 0

cin.get(c); // 讀一個非空白字元,EOF -> -1

讀字元

丟棄一個字元

 

cin.ignore();
string s;
getline(cin, s);

到第一個\n,讀成字串

從字串讀東西

string s;
// ...

stringstream ss(s);

int i, j, k;
ss >> i >> j >> k; // read as 3 integers
while(cin >> i) {

}

cin到EOF

string s;
while(getline(cin, s)) {

}
char c;
while((c = getchar()) != EOF) {

}

例題

 UVA 382,2024/12/10 CPE 題2

L1: 多個N 到0為止

int N;

// 讀 N 到 EOF or N = 0
while (cin >> N && N) { 
  // handle your inputs
}

例題

 UVA 11063,2025/05/20 CPE 題1

L1: 多個n (到EOF)

    L2: n個數

int N;
while(cin >> N) { // 讀N到EOF
  while(N--) { //讀N個b
    int b;
    cin >> b; // handle your inputs
  }
}

 UVA 10268,2024/12/10 CPE 題3

例題

L1: 多個n (到EOF)

    L2: 一行數

long long x;
while (cin >> x) {
  cin.ignore();
       
  string line;
  getline(cin, line);
  stringstream ss(line);

  long long c;
  while (ss >> c) {
    // handle your inputs
  }
}

例題

 UVA 941,2023/10/17 CPE 題6

L1: 給T,T個Sample

   L2: 一行字 + 一數

int T;
cin >> T;

int N;

string s;
while (T--) { // run T times
  cin >> s >> N;
  // handle your inputs
}

I/O Optmization

'\n' vs endl

'\n' vs endl

'\n' 就是一個換行字元

endl 是一個操作符,換行 + flush buffer

 

flush 是 flush buffer 用的操作符

或是呼叫 cout.flush() 也行

'\n' vs endl

所以,如果有需要大量換行,可以使用 '\n'

最後在 cout << flush;

或是 cout.flush();

不要一直使用 cout << endl;

#include <bits/stdc++.h>

using namespace std;

int main() {
  ios::sync_with_stdio(false);
  cin.tie(nullptr);
}

C/C++ I/O 解除綁定

(其實這個我也用不到)

IO優化基礎兩件套

ios::sync_with_stdio(false);

取消C++與C的 I/O綁定

(預設每次C++ I/O 時,會去檢查C的I/O Buffer)

ios::sync_with_stdio(false);

影響:

  • 不能同時使用cin / scanf
  • 不能同時使用cout / printf
cin.tie(nullptr);

取消cin與cout的綁定

(預設每次cin read前,都會flush一次cout)

cin.tie(&cout);
cin.tie(&cout);

只想過畢業門檻的人不用看

char unlocked

(這個屬於黑科技)

把 getchar() 改成 getchar_unlocked()

把 putchar() 改成 putchar_unlocked()

 

總之,這個東西沒有Thread Safety,稍快。

STL Container

Standard Template Library 內有這麼一些Container

用於存放/管理元素。

<Template> 可以存放任意型別的元素。

已經抽象過底層的資料結構,提供統一操作介面。

Iterator

每個容器皆有Iterator可以使用,

 

可以想像是: 指向容器元素的指標

但比指標更抽象

 

 

假設今天它是vector<int>的迭代器,
型別就是vector<int>::iterator
但我們可以用auto代替

通常,會有這四種取得迭代器的基本方法

(有些沒有,如stack / queue)

大多可以用 (+、-) (加、減) 來移動
a.begin() 指向第一個元素
*a.begin() 是第一個元素
a.begin()+1 指向第二個元素
*(a.begin()+1) 是第二個元素

但是其實我們不常使用Iterator。

我們只要先記:

 

  1. Iterator是元素的指標
  2.  *a.begin() 取第一個元素
  3. *a.rbegin() 取最後一個元素

string

宣告

string s1 = "Hello"; // "Hello"
string s2("world"); // "world"
string s3(5, "!"); // "!!!!!"

字元讀寫

string s = "Hello"; // "Hello"

char c = s[2]; // 'l'

s[1] = "a";

cout << s; // "Hallo"

字串長度

s.length()

s.size()

concatenation

string s1 = "Hello";
string s2 = "World";
string s3 = s1 + " " + s2; // "Hello World"

substring

string s = "Hello World";

// s.substr(offset, length)
// 從s[0]取五個字元
string s2 = s.substr(0, 5); // "Hello"

// s.substr(offset)
// 從s[6]取到尾
string s3 = s.substr(6); // "World"

cout << s; // "Hello World"

不改變原有的string內容

insert / erase / replace

string s = "Hello World";

// s.insert(offset, str)
// 在 s[offset] 前面插入
s.insert(5, ","); // "Hello, World"

// s.erase(offset, length)
// 從 s[offset] 開始刪除 length個字元
s.erase(5, 1); // "Hello World"

// s.replace(offset, length, newString)
// 從s[offset] 開始刪除 length 個字元,換成newString
s.replace(6, 5, "World!!!!!");  // "Hello World!!!!!"

cout << s; // "Hello World!!!!!"

改變原有的string內容

insert / erase / replace

string s = "Hello World";

// "Hello, World"
string s2 = s.substr(0, 5) + "," + s.substr(5); 

// "HelloWorld"
string s3 = s.substr(0, 5) + s.substr(5 + 1); 

// "Hello World!!!!!"
string s4 = s = s.substr(0, 6) + "World!!!!!" + s.substr(6 + 5); 

不改變原有的string內容

find

string s = "I like C++ and C++ is powerful";

// find where "C++" is
size_t pos = s.find("C++");

// 如果不是npos (一個常數,代表沒找著)
if (pos != string::npos) {
    cout << "第一次出現於: " << pos << "\n";
}

push_back / pop_back

string s = "123456";

s.push_back('7'); // "1234567"

s.pop_back(); // "123456"

s.pop_back(); // "12345"

cout << s; // "12345"

改變原有的string內容

number conversion

string s = "123";
int x = stoi(s); // string to int
double y = stod(s); // string to double

int i = 456;
string s = to_string(i); // int to string
cout << s; // 456

iteration

for (int i = 0; i < s.length(); i++) {
    cout << s[i];
}

for (char c : s) {
    cout << c;
}

vector

  • 動態陣列
  • O(1) 隨機存取 
  • O(1) 插刪尾
  • 底層: array

vector

宣告

vector<int> v; // {}
vector<int> v(5); // {0, 0, 0, 0, 0}

vector<int> v(3, 1); // {1, 1, 1}
vector<int> v = {1, 2, 3};

多層vector

vector<vector<int>> v2; // {}
vector<vector<int>> v2(5); // {{}, {}, {}, {}, {}}

想要多層vector,就是vector內有vector嘛

多層vector

// {{0, 0}, {0, 0}, {0, 0}}
vector<vector<int>> v2(3, vector<int>(2));

// {{4, 4}, {4, 4}, {4, 4}}
vector<vector<int>> v2(3, vector<int>(2, 4)); 

建構子第二個參數表示內層

操作

// 插尾
v.push_back(val) // O(1), O(n) on realloc
v.emplace_back(args) 

// 在it插入val,回傳val的it
v.insert(it, val) // O(n) worst
v.emplace(it, args)
    
// 在it插入n個val,回傳第一個val的it
v.insert(it, n, val); // O(n + n(move))

// 在it插入[itfirst, itend),回傳第一個val的it
v.insert(it, itbegin, itend); // O(n + m)

操作

// 刪尾
v.pop_back() // O(1)
    
// 刪除it的元素,回傳下一個有效iterator
v.erase(it) // O(n) worst
    
// 刪除[itfirst, itend),回傳下一個有效it
s.erase(itfirst, itend) // O(n)

操作

// 讀取第i個
int x = v[i] // O(1)

// 安全讀取第i個
int i2 = v.at(i) // O(1)

// 讀頭
int front = v.front() // O(1)

// 讀尾
int back = v.back() // O(1)

操作

// 查看是否為空
bool isEmpty = v.empty() // O(1)

// 清空
v.clear() // O(n)

// 查看大小
int size = v.size() // O(1)

三個基本方法

vector<int> v = {2,1,3};

v.push_back(4);

v.push_back(5);

v.pop_back();

v[1] = 6;

2

1

3

2

1

3

2

1

3

2

1

3

4

4

4

5

4

2

6

3

4

vector<int> v = {2,1,3};

v.push_back(4);

v.push_back(5);

v.pop_back();

v[1] = 6;

2

1

3

2

1

3

2

1

3

2

1

3

4

4

4

5

4

2

6

3

4

*v.begin()

iteration

for (int i = 0; i < v.size(); i++) {
    cout << v[i];
}

for (int i : v) {
    cout << i;
}

// 這裡 auto 應該是 vector<int>
for (auto v : v2) { 
  for (int i : v) {
    cout << i;
  }
}
    vector<int> v = {1,2,3,4,5};
    
    auto it = v.begin(); // 指向第1個元素
    int i = *it; // 第1個元素
    cout << i << "\n"; // 1

    auto it2 = v.begin() + 1; // 指向第2個元素
    int i2 = *it2; // 第2個元素
    cout << i2 << "\n"; // 2
    
    auto it3 = v.begin() + 2; // 指向第3個元素
    *it3 = 100; //直接更改iterator指向的元素
    cout << *it3; // 100
    cout << v[2]; // 100

接續前一篇的iterator: 例子

vector<int> v = {1,2,3,4,5};
    
for (auto it = v.begin(); it != v.end(); ++it) {
    cout << *it << " "; // 1 2 3 4 5
}

for (auto rit = v.rbegin(); rit != v.rend(); ++rit) {
    cout << *rit << " "; // 5 4 3 2 1
}

接續前一篇的iterator: 走訪vector

例題

翻譯:

每個T給10個網站 + 分數

輸出最高分數的網站

#include <bits/stdc++.h>

using namespace std;
int main() {
  int T,score;
  cin >> T;
  string s;
  
  vector<string> c;
  for (int t = 1; t <= T;t++) {
    cout << "Case #" << t << ":\n";
    int max = -1;
    for (int i = 0; i < 10; i++) {
      cin >> s >> score;
      if (score >= max) {
        if (score > max) {
          c.clear();
        }
        max = score;
        c.push_back(s);
      }
    }
        
    for(string s : c) {
      cout << s <<"\n";
    }
  }
}

deque

deque

  • 雙端佇列
  • 接近O(1) 隨機存取 
  • O(1) 插刪頭尾
  • 底層: segment array

宣告

deque<int> dq; // {}
deque<int> dq(5); // {0, 0, 0, 0, 0}

deque<int> dq(3, 1); // {1, 1, 1}
deque<int> dq = {1, 2, 3};

多層dq

deque<deque<int>> dq2; // {}
deque<deque<int>> dq2(5); // {{}, {}, {}, {}, {}}

想要多層dq,就是dq內有dq嘛

多層dq

// {{0, 0}, {0, 0}, {0, 0}}
deque<deque<int>> dq2(3, deque<int>(2));

// {{4, 4}, {4, 4}, {4, 4}}
deque<deque<int>> dq2(3, deque<int>(2, 4)); 

(與vector相同)

建構子第二個參數表示內層

操作

// 插尾
dq.push_back(val); // O(1) amortized
dq.emplace_back(args);

// 插頭
dq.push_front(val); // O(1) amortized
dq.emplace_front(args);

// 在it插入val,回傳val的it
dq.insert(it, val) // O(n) worst
dq.emplace(it, args)
    
// 在it插入n個val,回傳第一個val的it
dq.insert(it, n, val); // O(n + n(move))

// 在it插入[itfirst, itend),回傳第一個val的it
dq.insert(it, itbegin, itend); // O(n + m)

增 (比vector多了插頭)

操作

// 刪尾
dq.pop_back(); // O(1)

// 刪頭
dq.pop_front(); // O(1)

// 刪除it的元素,回傳下一個有效it
dq.erase(it) // O(n) worst
    
// 刪除[itfirst, itend),回傳下一個有效it
dq.erase(itfirst, itend) // O(n)

操作

// 讀取第i個
int i = dq[i] // 極接近O(1)

// 安全讀取第i個
int i2 = dq.at(i) // 極接近O(1)

// 讀頭
int front = dq.front() // O(1)

// 讀尾
int back = dq.back() // O(1)

(與vector相同)

操作

三個基本方法

// 查看是否為空
bool isEmpty = dq.empty() // O(1)

// 清空
dq.clear() // O(n)

// 查看大小
int size = dq.size() // O(1)
deque<int> dq = {2,1,3};

dq.push_back(4);

dq.push_front(5);

dq.pop_back();

dq[1] = 6;

2

1

3

2

1

3

2

1

3

5

2

1

4

4

4

5

3

5

6

1

3

deque<int> dq = {2,1,3};

dq.push_back(4);

dq.push_front(5);

dq.pop_back();

dq[1] = 6;

2

1

3

2

1

3

2

1

3

5

2

1

4

4

4

5

3

5

6

1

3

*dq.begin()

stack & queue

Stack:

  • 先入後出
  • 括號/字串處理
  • DFS
  • 遞迴 (模擬call stack)
  • Backtracking

資料結構應該都學過

Queue:

  • 先入先出
  • unweighted BFS/pathfinding
  • Sliding Window

這兩個在STL中,底層都是以deque實現

 

Stack

push_back() / pop_back()

back()

宣告

stack<int> s; 

操作

// push val
s.push(val); // O(1)
s.emplace(args);

// pop
s.pop(); // O(1)

增刪

也就是deque的push_back(val)與pop_back()

操作

// 讀取頂部元素
int i = s.top();

也就是deque的back()

操作

// 查看是否為空
bool isEmpty = s.empty() // O(1)

// 查看大小
int size = s.size() // O(1)

兩個基本方法

操作

while (!s.empty()) {
  s.pop();
}

清空

s = stack<int>();

重新建立空的stack

stack<int> s;

s.push(5);

s.push(4);

s.push(3);

s.pop();

s.push(7);

5

4

5

4

3

5

5

4

s.top()

5

4

7

5

4

3

4

7

stack<int> s;

s.push(5);

s.push(4);

s.push(3);

s.pop();

s.push(7);

5

4

5

4

3

5

5

4

s.top()

5

4

7

5

4

3

4

7

例題

翻譯:

T行

Sleep: 入某人的夢

Kick: 退出一層

Test: 在誰的夢裡

#include <bits/stdc++.h>

using namespace std;

int main() { 

  stack<string> dreams;
  
  int T;
  cin >> T;
  
  while (T--) {
    string s;
    cin >> s;
    if (s == "Sleep") {
      string name;
	  cin >> name;
	  dreams.push(name);
	} else if(s == "Kick") {
	  if(!dreams.empty())
	    dreams.pop();
	} else if(s == "Test") {
	  if(dreams.empty())
	  	cout << "Not in a dream\n";
	  else 
	  	cout << dreams.top() <<"\n";
	}
  }
}

Queue

push_back() / pop_front()

back() / front()

宣告

queue<int> q; 

操作

// push val
q.push(val); / q.emplace(val) // O(1)

// pop
q.pop(); // O(1)

增刪

也就是deque的push_back(val)與pop_front()

操作

// 讀取 front
int front = s.front();

// 讀取 back
int back = s.back();

與deque相同

操作

// 查看是否為空
bool isEmpty = q.empty() // O(1)

// 查看大小
int size = q.size() // O(1)

兩個基本方法

操作

while (!q.empty()) {
  q.pop();
}

清空

q = stack<int>();

重新建立空的stack

queue<int> s;

s.push(5);

s.push(4);

s.push(3);

s.pop();

s.push(7);

5

4

5

4

3

5

4

3

4

3

7

5

5

5

4

4

5

4

3

3

7

s.front()

s.back()

queue<int> s;

s.push(5);

s.push(4);

s.push(3);

s.pop();

s.push(7);

5

4

5

4

3

5

4

3

4

3

7

5

5

5

4

4

5

4

3

3

7

s.front()

s.back()

例題

抱歉,沒有找到簡單的題目。

  • UVA 439 (DFS)
  • UVA 532 (3D DFS)
  • UVA 928 (DFS)
  • UVA 122 (拆樹)

set

  • 就是集合。
  • 不允許重複元素
  • 自動排序: 按照值的 < 排序
  • 無法random access

set

宣告

set<int> s;
#include <bits/stdc++.h>

using namespace std;

struct CompareMethod {
    bool operator()(const int &a, const int &b) const {
        return a > b; // 改成大到小
    }
};

int main() {
    set<int, CompareMethod> s = {1, 5, 3, 2, 4};

    for (int x : s) 
      cout << x << " "; // 5 4 3 2 1
}

排序方法預設是按照物件類型預設的 < 排序法,

像是 int 的 <  是 "小在前,大在後"

你可以使用自訂的排序法

操作

// 插入val,回傳pair<iterator, bool>(val的it, 是否是新元素)
s.insert(val) // O(log n)
s.emplace(args)
    
// 插入[itfirst, itend)
s.insert(itbegin, itend); // O(n + m)

操作

// 刪除元素,回傳刪除的個數 (0 or 1)
s.erase(val) // O(log n)

// 刪除it的元素,回傳下一個有效it
s.erase(it) // O(log n)

// 刪除[first it, end it),回傳下一個有效it
s.erase(itfirst, itend) // O(log n)

操作

// 回傳指向key的it,沒有的話回傳s.end()
s.find(key) // O(log n)
    
// 回傳key個數 (0 or 1)
s.count(key) // O(log n)
    
// 回傳指向 "第一個>= key的值" it
s.lower_bound(key) // O(log n)
    
// 回傳指向 "第一個> key的值" it
s.upper_bound(key) // O(log n)

操作

三個基本方法

// 查看是否為空
bool isEmpty = s.empty() // O(1)

// 清空
s.clear() // O(n)

// 查看大小
int size = s.size() // O(1)
set<int> s;

s.insert(6);

s.insert(4);

s.insert(5);

s.erase(4);

s.push(7);

6

6

4

5

6

4

5

6

5

6

7

6

4

4

5

5

*s.begin()

iteration

for (int i : s) {
    cout << i;
}

例題

翻譯:

給你一個文章

要你把出現過的單字,字典序,小寫輸出

看到字典序應該就想到能用set放字串了吧?

#include<bits/stdc++.h>

using namespace std;

int main() {
    
    set<string> words;
    
    string s;
    char c;
    
    
    while ((c = getchar()) != EOF) {
        if (isalpha(c)) {
            s += tolower(c);
        } else {
            words.insert(s);
            s = "";
        }
    }
      
    words.erase("");
    for(string w : words) {
        cout << w << "\n";
    }
}

pair

能存儲兩個不同類型的值

成員: first, second

以first < 再來 second < 排序

pair

今天假設你有個Student,有id跟name:
 

struct Student {
    int id;
    string name;
};
Student s = {31, "蔡詠竣"};

我,身為一個懶人,偶爾想用pair<int, string> 代替。
 

pair<int, string> me = {31, "蔡詠竣"};

宣告

pair<int, string> p;

p.first = 31; // 更改first
p.second = "蔡詠竣"; // 更改second

pair<int, string> p2 = make_pair(31, "蔡詠竣");
pair<int, string> p3(31, "蔡詠竣");
pair<int, string> p4 = {31, "蔡詠竣"};
vector<pair<int, string>> students;
students.push_back({31, "蔡詠竣"});

pair<int, string> me = students[0];

cout << me.first << " " << me.second;

一樣可以放於vector內,偶爾蠻好用的。

像這樣就是一個可以random access的k-v pair。

不用寫兩個vector對著。

pair<int, string> p1 = {31, "蔡詠竣"},
                  p2 = {30, "張乃文"},
                  p3 = {42, "黃芋泥"};

vector<pair<int, string>> v{p1, p2, p3};

for (auto i : v) { // 把 pair<int, string> 用 auto 代替
    cout << i.first << " " << i.second << "\n";
}

cout << "\n";

sort(v.begin(), v.end()); // 排序

for (auto i : v) {
    cout << i.first << " " << i.second << "\n";
}}

前面說過了,使用first < 排序

31 蔡詠竣
30 張乃文
42 黃芋泥

30 張乃文
31 蔡詠竣
42 黃芋泥

tuple

能存儲>=3個不同類型的值

成員存取方式稍有不同

從第一個元素排,再來第二個,...

tuple

簡單講就是超級懶/臨時想用 可以用。

使用方法

tuple<int, int, int> coord = {1, 2, 3};

int y = get<1>(coord); // 2

get<2>(coord) = 4; // coord 變成 {1, 2, 4}

vector<tuple<int,int,int>> v;
v.push_back(coord);
v.emplace_back(10, 20, 30);

for (auto &[x,y,z] : v) { // C++17 綁定大法
    cout << x << " " << y << " " << z << "\n";
}

map

 

  •  每一個元素都是(key, value)形式,也就是pair。
  • key不能重複
  • 自動排序: 按照 key 的 < 排序
  • 底層: RB-tree

 

map

可以想像成是set<pair<K,V>>

或是不排序的vector<V>      (K是數字)

宣告

map<int, string> m;
map<int, string> m2 = {{1,"one"}, {2,"two"}};

操作

// 如果k不存在,插入{k, v},如果存在,更新v
m[k] = v; // O(log n)

// 插入p,回傳pair<iterator, bool>(p的it, 是否是新元素)
m.insert(pair<K, T> p); // O(log n)
m.emplace(k, v)

// 插入[itfirst, itend)
m.insert(itbegin, itend) // O(m log(n+m))

操作

// 刪除key,回傳刪除的個數 (0 or 1)
m.erase(key) // O(log n)
    
// 刪除it的元素,回傳下一個有效it
m.erase(it) // O(log n)
    
// 刪除[first it, end it),回傳下一個有效it
m.erase(itfirst, itend) // O(k log n)

操作

// 回傳指向key的it,沒有的話回傳m.end()
m.find(key) // O(log n)
    
// 回傳key個數 (0 or 1)
m.count(key) // O(log n)
    
// 回傳指向 "第一個>= key的值" it
m.lower_bound(key) // O(log n)
    
// 回傳指向 "第一個> key的值" it
m.upper_bound(key) // O(log n)

操作

// 存取key為k的value
// 如果不存在,會插入{k, (default value)},並回傳value。
m[k]; // O(log n)

操作

三個基本方法

// 查看是否為空
bool isEmpty = m.empty() // O(1)

// 清空
m.clear() // O(n)

// 查看大小
int size = m.size() // O(1)
map<int, string> m;

m[2] = "b";

m[1] = "a";

m[3] = "c";

m.erase(2);

{2,"b"}

{1,"a"}

{1,"a"}

{2,"b"}

{3,"c"}

{3,"c"}

{2,"b"}

{1,"a"}

什麼時候用map ? 什麼時候用vec/dq ?

map: 需要排序、key不知道範圍、key不是數字

vec/dq: 不用排序(排序也行),key有範圍

例題

翻譯:

給你 英文 + 外文

再來,給 外文 要你找 英文

#include <bits/stdc++.h>
using namespace std;

int main() {
    _____________________ dict;
    string line, eng, foreign;

    while (getline(cin, line) && line != "") {
        stringstream ss(line); // 拆分 line 成兩個字串
        ss >> eng >> foreign;
        _____________________;
    }

    while (cin >> foreign) {
        if (_____________________)
            cout << _____________________ << "\n";
        else
            cout << "eh\n";
    }
}

通用方法

find(v.begin(), v.end(), val);

查找val的it

Find

查找val的出現次數

count(v.begin(), v.end(), val);

Count

把元素reverse

reverse(v.begin(), v.end());

Reverse

vector<int> v = {1, 2, 3, 4, 5};

// reverse vector
reverse(v.begin(), v.end()); 

for (int x : v) // 5 4 3 2 1
    cout << x << " ";

Reverse

回傳bool, 看val在不在

binary_search(v.begin(), v.end(), val);

binary_search

排序

sort(v.begin(), v.end());

Sort

sort(v.begin(), v.end(), [](int a, int b)) {
    /*
      a在b前 -> return true,反之false
    */
    return a > b;
}

自訂排序 (int為例)

例題

 UVA 10268,2024/10/15 CPE 題2

翻譯:

每加一個數,找一次中位數

#include <bits/stdc++.h>

using namespace std;

int main() {
    int i;
    vector<int> v;
    while(cin >> i) {
        v.push_back(i);
        sort(v.begin(), v.end());
        if(v.size() % 2 == 1) {
            cout << v[v.size()/2] << "\n";
        } else {
            cout << (v[v.size()/2] + v[v.size()/2-1])/2 << "\n";
        }
    }
}

一些utilties

bool b = isalpha(c);
bool b = isdigit(c);
c = toupper(c);
c = tolower(c);

字元處理

int gcd = __gcd(a, b);
int lcm = lcm(a, b);

gcd & lcm

string s = "10110101";

bitset<8>(s).to_ulong();

number to bits string

bits string to ulong

bitset也是STL容器之一,我覺得還不用細講。

int i = 12345;

bitset<32>(i).to_string();

質數表

從我大一到大三,這題出現過兩次了

其實是反質數表

int size = 1000001;
bool notprime[1000001] = {};
notprime[0] = true;
notprime[1] = true;

int lim = sqrt(size);
for (int i = 2; i <= lim; i++) {
    if (!notprime[i]) {
        int k = i * i;
        while(k < size) {
            notprime[k] = true;
            k += i;
        }
    }
}

// 看100是不是質數
bool isPrime = !notprime[100];

例題

2025/05/20 CPE 題4

翻譯:

給多個n (T)

找n = a + b,a <= b,a和b都是質數

題解過長

最後

謝謝各位。

程式競賽

By speedcubing

程式競賽

  • 122