最後更新日期 2024 / 01 / 01

C++ 常見 STL 教學

piar

簡介

pair 是一種可以儲存兩個元素的資料結構

用途

通常用來儲存二維座標或者是陣列的 index 與 value
而且常數較小
如果用 array / vector 替代有些題目真的會被卡 TLE

語法

// 宣告一個第一個欄位為 1 第二個欄位為 5 的 pair
// 在一些 c++ 版本較舊的環境下要使用 make_pair(1, 5)
pair<int, int> pos = {1, 5};

// 取得第一個欄位的值
cout << pos.first << endl;

// 取得第二個欄位的值
cout << pos.second << endl;

// 將 pos 的第一個欄位的 1 改成 2
pos.first = 2;

// 將 pos 的第二個欄位的 5 改成 4
pos.second = 4;

比較

在比較兩個 pair 時會先比第一個元素
如果第一個元素相同才會比第二個元素

vector

簡介

跟傳統陣列差不多
不過長度是可變的
會因為加入更多元素讓 vector 變得更長

用途

不用多說了吧
就是可以像陣列一樣儲存 n 個元素

語法

// 直接宣告 vector 的方式
vector<int> v1;

// 宣告長度為 n 的 vector
vector<int> v2(n);

// 宣告長度為 n 且每個元素都是 5 的 vector
vector<int> v3(n, 5);

// 宣告一個有元素 {1, 2, 3, 4} 的 vector
vector<int> v4 = {1, 2, 3, 4}

// 宣告一個跟其他 vector 一樣的 vector (複製)
vector<int> v5(v4);

// 加入元素在尾端
v1.push_back(7);

// 從尾端移除元素
v1.pop_back();

// 存取 vector 中的某一項
cout << v4[2] << endl;

// 取得大小
cout << v2.size() << endl;

比較

從第一個元素開始比較
如果相同就繼續往後比
直到第一個不同為止

queue

簡介

像是一個排隊的隊伍
可以從一端進入
從另一端離開
也就是先進去的會先離開

用途

大部分情況用來實作一些較複雜的演算法
比如 BFS、拓撲排序等等

語法

// 宣告一個 queue
queue<int> q;

// 將元素 5 推入 queue 後端
q.push(5);

// 取得 queue 前端的元素
cout << q.front() << endl;

// 取得 queue 後端的元素
cout << q.back() << endl;

// 將隊伍前端的元素彈出(移除)
q.pop();

// 檢查 queue 是否為空
if (q.empty()) {
    cout << "empty" << endl;
}

// 取得 queue 的大小
cout << q.size() << endl;

stack

簡介

類似於一疊盤子
最後放上去的盤子會最先被取走
而你不能從中間拿走盤子
也就是說後進去的會先出去

用途

stack 常用來會處理一些匹配類問題
或者是模擬遞迴
更進階一點還有單調堆疊之類的玩法

語法

// 宣告一個 stack
stack<int> stk;

// 將元素 3 推入 stack
stk.push(3);

// 從 stack 頂端取出元素
cout << stk.top() << endl;

// 將 stack 頂端的元素彈出(移除)
stk.pop();

// 檢查 stack 是否為空
if (stk.empty()) {
    cout << "empty" << endl;
}

// 取得 stack 的大小
cout << stk.size() << endl;

set

簡介

就是集合的意思
用來儲存不重複的元素
並會按照元素的大小自動排序

用途

主要用於快速插入和查找元素

語法

// 宣告一個 set
set<int> st;

// 插入元素
st.insert(5);
st.insert(3);
st.insert(7);

// 檢查 set 是否包含某元素
if (st.find(3) != st.end()) {
    cout << "set contains 3" << endl;
} else {
    cout << "set does not contain 3" << endl;
}

// 刪除元素
st.erase(5);

// 遍歷 set 中的元素
for (int e : st) {
    cout << e << " ";
}

// 取得 set 的大小
cout << st.size() << endl;

// 取得 set 的第一項
// 因為回傳的是 iterator 所以要加 *
cout << *st.begin() << endl;

// 取得 set 的最後一項
// 由於 back 是開區間所以要 -1
cout << *--st.end() << endl;

map

簡介

語法根陣列有點像
不過可以接受整數以外的型態來當作索引值

用途

有些時候需要使用負數、非常大的數字、字串等等來當索引值
map 就會是一個很方便的工具

語法

// 宣告一個以 string 為索引,int 為值的 map
map<string, int> mp;

// 把字串對應到數字
mp["one"] = 1;
mp["two"] = 2;
mp["three"] = 3;

// 輸出 one 的值
cout << mp["one"] << endl;

// 在 map 中刪除 one
mp.erase("one");

// 遍歷 map
for (auto [key, value] : mp) {
    cout << key << ", " << value << endl;
}

// 取得 map 的大小
cout << mp.size() << endl;

priority queue

簡介

又稱為 heap
可以支援快速地插入及查詢最大最小值

用途

很常使用在一些複雜的演算法內
比如最短路徑、最小生成樹等等

語法

// 宣告一個 priority queue
priority_queue<int> pq;

// 宣告一個 priority queue 並且頂部為最小值
priority_queue<int, vector<int>, greater<int>> pq;

// 插入元素
pq.push(3);
pq.push(1);
pq.push(5);

// 取得頂端的元素(最大值)
cout << pq.top() << endl;

// 移除頂端的元素
pq.pop();

// 檢查 priority queue 是否為空
if (pq.empty()) {
    cout << "empty" << endl;
}

// 取得 priority queue 的大小
cout << pq.size() << endl;

推薦文章

題解 CSES Restaurant Customers

給你 n 位客人的進入和離開餐廳的時間 輸出最多同時有幾位客人同時在餐廳- n le 2 times 10^5把每個人加入時間和離開的時間分別看成兩個時間點 並把所有時間點加入陣列後一起排序 然後用一個變數來維護目前餐廳人數 每次遇到加入就增加 反之遇到離開就減少 維護中遇到的最大值就是答案了 時間複雜度:Oncpp using namespace std; signed main { int n; cin n; vectorpii v; for int i = 0; i n; i++ { int s, t; cin s t; v.pushback{s, true}; v.pushback{t, false}; } sortv.begin, v.end; int cur = 0; int ans = 0; for auto e : v { if e.ss == true cur++; else cur--; ans = maxcur, ans; } cout ans endl;}

CSES

題解 CSES Sum of Divisors

定義 sigmax 為 x 的所有因數之和 給予一個 n 求 sum^n{k=1}sigmak 之值以 12 為例 先看一下這張圖列出了 1 sim 12 的所有因數可以發現每兩個數字就會出現一次 2 每三次數字就會出現一次 3 觀察後可知 對於一個數字 x 它的出現次數會是 lfloor frac{n}{x} rfloor 因為每 x 個數字就會出現一個 x 的倍數 用這個做法枚舉每個數字計算就可以得到一個 On 的算法 但這樣還不夠快到足以處理這題 10^{12} 的資料量如果我們把剛剛那張圖的呈現方式改一下 上面那一橫排代表因數 1 sim 12 下面那一行排代表該因數出現的次數可以發現這張圖內有幾串連續的因數出現次數相同 因為事實上在計算這個的過程中只會出現最多 2 sqrt n 個相異數字 證明上面那張圖每個紅色圈起來代表一塊 如果我們可以塊為單位來計算就可以把時間複雜度降到 Osqrt{n} 了想像一下 如果 n 除以 x 可以剛好整除的話 令 k = lfloor frac{n}{x} rfloor 則 lfloor frac{n}{k} rfloor 必定等於 k 那如果不是剛好整除的呢? lfloor frac{12}{5} rfloor = 2 ,quadlfloor frac{12}{2} rfloor = 6 再試試看 6 lfloor frac{12}{6} rfloor = 2 ,quadlfloor frac{12}{2} rfloor = 6 觀察後發現 這裡的 6 就是最大的 x 滿足 frac{n}{x} 為 2 於是這樣我們就可以找到最後一個連續出現次數相同的因數 最後只要把每次一塊的第一個數和最後一個數用梯形公式求總和乘上共同的出現次數就可以得到答案了不過這邊要注意的是 因為答案很大所以題目要我們對 10^9 + 7 取模 但梯形公式中又會用到除法 所以我們不能直接除 需要先求出模反元素.modulo-inverse再乘進去才行cppusing namespace std;const int mod = 1e9 + 7;const int inv = 500000004; signed main { int n; cin n; int cur = 1; int ans = 0; while cur = n { int cnt = n cur; int last = n cnt; int sum = last + cur % mod last - cur + 1 % mod % mod inv % mod; ans += sum % mod cnt % mod % mod; cur = last + 1; } cout ans % mod endl;}

CSES

Codeforces 918 Div 4 題解

為了慶祝第一次在 CF 破台所以難得來寫一下題解 這成績我可以開心一個禮拜了 : ~~然後今天早上過的太廢一題都沒寫不知道是不是因為這樣把能量都留到這場釋放了~~給定三個數,其中兩個數相同,找到不同的那一個數直接照題意模擬做就行了,實作的部分用 map 很方便cppusing namespace std; int main { int t, tmp; cin t; while t-- { mapint, int cnt; 計算每個數字出現幾次 for int i = 1; i = 3; i++ { cin tmp; cnttmp++; } 找到出現次數為 1 的那個數 for auto num, frq : cnt { if frq == 1 cout num endl; } }}給予一個 3x3 的矩陣,每行、列都剛好包含各一個 A、B、C 字母,但某一格未知,任務是找到未知的那格是什麼直接照題意模擬做就行了,實作的部分用 set 很方便cppusing namespace std;char tb55; 檢查矩陣是否合法bool check { 檢查每個 row for int i = 0; i 3; i++ { setchar st; for int j = 0; j 3; j++ { st.inserttbij; } if st.size = 3 return false; } 檢查每個 column for int i = 0; i 3; i++ { setchar st; for int j = 0; j 3; j++ { st.inserttbji; } if st.size = 3 return false; } return true;}int main { int t; cin t; while t-- { int px, py; 未知格的座標 for int i = 0; i 3; i++ { for int j = 0; j 3; j++ { cin tbij; if tbij == '?' { px = i; py = j; } } } 枚舉該放哪個字母 for char c = 'A'; c = 'C'; c++ { tbpxpy = c; 如果合法就輸出 if check { cout c endl; } } }}有 n 個盒子,第 i 個盒子裝著 ai 個 1x1 的紙片,求是否可以剛好用所有的紙片拼成一個平片的正方形?事實上題目問的就只是 sum{i=1}^n ai 是否為完全平方數 所以只需要加總後開根在平方看是否跟原始的數相等就行了cppusing namespace std;signed main { int t; cin t; while t-- { int n, tmp, sum = 0; cin n; for int i = 0; i n; i++ { cin tmp; sum += tmp; } if squareintsqrtsum == sum { cout "YES" endl; } else { cout "NO" endl; } }}有一種只包含 a、b、c、d、e 的語言,這些字母被分成兩種類型- Vowels — 包含字母 a 和 e,用 V 表示- Consonants — 包含字母 b、c、d,用 C 表示一個合法的段落可能為 CV 或 CVC 給予一串文字 求將它按照規則分段的結果直接隨便取的話會出一些問題 不過其實只需要觀察到兩個 C 不能連再一起就完事了 雖然我也還沒完整的證過 因為我不是數學家cppusing namespace std; 儲存每個字母是 v 或 c 類mapchar, char tb = { {'a', 'v'}, {'b', 'c'}, {'c', 'c'}, {'d', 'c'}, {'e', 'v'},};void sol { int n; string s; 題目給的字串 string ss; 轉換過後只包含 c 跟 v 的字串 cin n s; 轉換字串 for auto c : s ss += tbc; 分段後的結果 vectorstring ans; for int i = 0; i s.size; { if i == s.size - 3 { 只剩下最後三個字就直接全取 ans.pushbacks.substri, 3; break; } if ssi+3 == ssi+2 ssi+2 == 'c' { 如果選 CV 會導致兩個連續 C 的情況出現就改選 CVC ans.pushbacks.substri, 3; i = i + 3; } else { 否則就選 CV ans.pushbacks.substri, 2; i = i + 2; } } 輸出解 for int i = 0; i ans.size; i++ { if i = 0 cout "."; cout ansi; } cout endl;} int main { int t; cin t; while t-- sol;}給定一個陣列,求是否存在一段連續的區間滿足偶數 index 的所有元素加起來等於奇數 index 的元素把偶數 index 的元素都加上負號再套上前綴和之後 題目就被轉換成找到一個區間滿足總和為 0也就是說只要從左掃到右 並對於每個位置檢查左邊是否存在相同值的元素即可cppusing namespace std; void sol { int n; cin n; vectorint an; for int i = 0; i n; i++ { cin ai; if i % 2 == 0 ai = -1; } vectorint psumn + 1; partialsuma.begin, a.end, psum.begin + 1; setint st; for auto e : psum { if st.finde = st.end { cout "YES" endl; return; } st.inserte; } cout "NO" endl;} signed main { int t; cin t; while t-- sol;}有 n 個人在數線上以每秒 1 格的速度往右走,第 i 個人的起點為 ai 終點為 bi,求他們倆倆碰面(同時出現在同一格)的次數仔細觀察之後會發現 只有 ai aj 且 bi bj 的情況下 i 跟 j 才會碰面 也就是一個區間把另外一個區間包住的情況可以發現其實就跟 CSES 的 Nested Ranges Count 完全一樣 那這題網路上題解很多 算法細節就不多提了cppusing namespace std;using namespace gnupbds;template class T using orderedset = treeT, nulltype, lessT, rbtreetag, treeorderstatisticsnodeupdate;void sol { int n; cin n; vectorint beconn, yidxn; vectortupleint, int, int bn, cn; for int i = 0; i n; ++i { int l, r; cin l r; bi = {l, r -1, i}; ci = {r, l -1, i}; } sortb.begin, b.end; sortc.begin, c.end; for int i = 0; i n; ++i yidxget2bi = i; orderedsetint s; for int i = 0; i n; ++i { int idx = get2ci; int cur = yidxidx; beconidx = s.size - s.orderofkeycur; s.insertcur; } int ans = 0; for int i = 0; i n; ++i ans += beconi; cout ans endl;} signed main { io; int t; cin t; while t-- sol;}給一張無向帶權圖,第 i 條邊連接著 vi 及 ui 且權重為 wi,你可以在節點 i 拿到一台速度為 si 的腳踏車,騎著速度為 si 的腳踏車經過第 j 條邊時需要花費 si times wj,求全程騎腳踏車從 1 走到 n 的最少時間花費不難想到從點 v 獲得腳踏車後到其他每個點 u 的最少時間花費為 distv, u sv 其中 dist 代表兩點最短路徑長度 那其實就只要依照上面的規則把那些邊都建在新的圖上 最後對新的圖做最短路就可以了補充資料: 最短路演算法cppusing namespace std;const int maxn = 1e3 + 10;const int inf = 1e13; int n, m;vectorpii gmaxn;int bikemaxn; vectorpii g2maxn;int dist2maxnmaxn; int distmaxn; void sol { 初始化 讀取輸入 cin n m; for int i = 0; i = n; i++ { for int j = 0; j = n; j++ { dist2ij = inf; } } for int i = 0; i = n; i++ { gi.clear; g2i.clear; } for int i = 0; i m; i++ { int a, b, w; cin a b w; ga.pushback{b, w}; gb.pushback{a, w}; } for int i = 1; i = n; i++ { cin bikei; } 在原始的圖上求每個點對得最短路 for int v = 1; v = n; v++ { 初始化 for int i = 1; i = n; i++ { disti = inf; } 使用 dijkstra 演算法 priorityqueuepii, vectorpii, greaterpii pq; pq.push{0, v}; distv = 0; while pq.size { auto now = pq.top; pq.pop; if now.ff distnow.ss continue; for auto other : gnow.ss { if distother.ff distnow.ss + other.ss { distother.ff = distnow.ss + other.ss; pq.push{distother.ff, other.ff}; } } } 建邊在新的圖上 for int i = 1; i = n; i++ { if i == v continue; g2v.pushback{i, disti bikev}; } } 初始化 準備在新的圖上做最短路 for int i = 1; i = n; i++ { disti = inf; } 使用 dijkstra 演算法 priorityqueuepii, vectorpii, greaterpii pq; pq.push{0, 1}; dist1 = 0; while pq.size { auto now = pq.top; pq.pop; if now.ff distnow.ss continue; for auto other : g2now.ss { if distother.ff distnow.ss + other.ss { distother.ff = distnow.ss + other.ss; pq.push{distother.ff, other.ff}; } } } 輸出解 cout distn endl;} signed main { int t; cin t; while t-- sol;}

Codeforces