读这一页之前,先知道读完要能做什么
- 写出来开一个 vector、往末尾加元素、按下标取第 i 个
- 写出来用
for遍历一遍,以及用下标遍历(知道什么时候该用后者) - 写出来「把符合条件的元素挑进一个新 vector」—— 这一题会反复用到
这是什么
一句话:vector 就是一个「长度会变的数组」。
★ 尖括号里写「装什么」:vector<int> 装整数,vector<char> 装字符,vector<string> 装字符串。
★ 下标从 0 开始。第 0 个、第 1 个……一直到 size() - 1。
完整动作表
复杂度写的是这个动作自己的代价,不是整个程序的。「这一栏最容易错的地方」那一列是重点。
| 写法 | 干什么 | 代价 |
|---|---|---|
vector<int> a; | 开一个空的 ⚠ vector<int> 装 int,vector<char> 装 char,两个是不同的类型 | O(1) |
vector<int> a(n); | 开 n 个,全是 0 ★ 读完 n 之后开数组,这是每道题的第一句 | O(n) |
vector<int> a(n, 7); | 开 n 个,全是 7 第二个参数是「每一格的初值」 | O(n) |
a.push_back(x) | 在末尾加一个 x ★★ 这是最高频的一个动作 | O(1) |
a[i] | 取第 i 个(从 0 数) ⚠ 最后一个是 a[a.size() - 1],不是 a[a.size()] | O(1) |
a.size() | 现在有几个 ⚠ 和 int 比大小要写 (int)a.size() < n | O(1) |
a.back() | 取最后一个 空 vector 上调它会出事 —— 先判 a.empty() | O(1) |
a.pop_back() | 扔掉最后一个(不给返回值) ⚠ 要那个值得先 x = a.back(); 存下来 | O(1) |
a.empty() | 是不是空的 比 a.size() == 0 短 | O(1) |
for (int x : a) | 依次取每个元素,当作 x 用一遍 ★★ 只要元素本身、拿不到下标时用它 | O(n) |
for (int i = 0; i < (int)a.size(); i++) | 按下标遍历,手里拿着 i ★★ 需要知道「第几个」时用它 —— ⚠ 和上面那个别搞混 | O(n) |
a.clear() | 长度清零 | O(n) |
swap(a[i], a[j]) | 把第 i 个和第 j 个对调 标准库自带的,不用自己写交换 | O(1) |
vector<vector<int>> g(R, vector<int>(C, 0)); | R 行、每行 C 个 0 的二维表 ★★ 动态规划的表全是这个写法;⚠ 内层那个 (C, 0) 不能省 | O(R·C) |
⭐ 这一节最容易错的地方(1 处)
这一节排在例题前面是故意的:读到一个坑,再去看正确的例子,记得住。 下面每一条都是「编译得过去、答案可能是错的」那种 —— 它们才是 OI 里真正扣分的地方。
只有一处:`a[i]` 下标越界不会报错
a[i] 的 i 超范围时,程序不会报错、不会崩在那一句,它会去读一块不属于它的内存 —— 拿到的可能是别的变量的值,也可能是 0(于是你以为它「有默认值」)。
★ 所以判断循环次数时那个 (int) 一定要写:for (int i = 0; i < (int)a.size(); i++)。写 i < a.size() 时两边都是无符号数,比较的行为会变得很别扭。本书哪一章真的用到它
⚠ 下表里的文件路径和那串 probe 是逐字验过的(check:docs 第 ⑳ 条)—— 改了那份代码这儿会红,正是要的。点章号直接去看那一页。
| 章 | 书里的哪一份代码 | 用它来干什么 |
|---|---|---|
| 第 1 章 | code/01-recursion-intro/p1255.cpp | ★ 全书第一次出现 vector —— 从第 1 章就在用 |
| 第 29 章 | code/29-graph-storage/b3643.cpp | ★★ 当邻接表用:vector<vector<int>>。那一章专门讲了三种存法的选型 |
| 第 53 章 | code/53-hld/fast.cpp | ⚠ 一个反例:那一章刻意改用静态数组,因为节点数上界写死在数据范围里 |
例题(2 道)
每道都给全套:题面 → 样例 → 参考代码 → 一步步的讲解。先自己读题想一遍,再看代码。
例题 ① 把符合条件的挑进一个新 vector
给一串整数和一个 k,输出其中所有大于等于 k 的数,按原来的先后顺序。
★ 这一题要练的就是 OI 里最常用的一招:开一个新的 vector,把要留的元素放进去。
n 和 k。第二行 n 个整数。>= k 的那串数。输入
9 4 3 7 2 9 4 6 1 8 5
输出
n = 9,k = 4 原序列: 3 7 2 9 4 6 1 8 5 >= 4 的数(6 个): 7 9 4 6 8 5
别想着「把不要的删掉」。反过来想:要留的是哪些?
// STL 专题 · vector —— 把要留的放进一个新 vector//// 这一份只练一件事:**新建一个 vector,把符合条件的元素放进去。**//// 这是 OI 里最常用的写法,比 v.erase 短、也不会出错:// vector<int> b;// for (int x : a) if (条件) b.push_back(x);//// ⚠ 输入第一行 n、第二个数 k:输出原序列里所有 >= k 的数,按原来的先后顺序。
#include <bits/stdc++.h>using namespace std;
int main() { ios::sync_with_stdio(false); cin.tie(nullptr);
int n, k; cin >> n >> k;
vector<int> a(n); for (int &x : a) cin >> x;
// ★ 全部内容就这三行:开一个新的,把要留的 push_back 进去。 vector<int> b; for (int x : a) { if (x >= k) b.push_back(x); }
cout << "n = " << n << ",k = " << k << "\n\n";
cout << "原序列:\n"; for (int x : a) cout << x << ' '; cout << "\n\n";
cout << ">= " << k << " 的数(" << b.size() << " 个):\n"; for (int x : b) cout << x << ' '; cout << '\n'; return 0;}点「运行 ▶」看结果
- 第一行:读进来。
vector<int> a(n)开 n 个,然后for (int &x : a) cin >> x;读进去。 - ★ 注意这里的
&:for (int &x : a)里的&意思是「直接改 a 里的那一格」,所以cin >> x真的填进去了。写成for (int x : a)就是复制一份出来,读完 a 还是空的。 - 核心就这三行:
vector<int> b;开一个新的,for (int x : a)扫一遍,if (x >= k) b.push_back(x);把要留的放进去。 - 最后
for (int x : b) cout << x << " ";打出来。
★★ 「开一个新的,把要留的挑进去」是 OI 里用得最多的 vector 写法。它比「边扫边删」短,也不会因为下标错位而漏掉元素。
用到的写法:vector<int> a(n); a.push_back(x) for (int x : a) a.size()
例题 ② 把 vector 当栈用
给一串只含 () [] {} 的字符,判断括号能不能配平。
★ 这一题练的是「vector 当栈」—— OI 里非常常用,而它不需要 stack。
( ) [ ] { } 组成。输入
([]{}[])输出
串:([]{}[])
配平 ✓左括号进 push_back,右括号去和最后一个没配上的左括号比 —— 那不就是 back() 吗。
// STL 专题 · vector —— 当栈用(OI 里最常见的两种用法之一)//// ★ 一句话:`vector<int> st;` + `push_back` / `back` / `pop_back` 就是栈。// ⚠ 本书里 `stack<int>` 只出现在 4 份代码,而 `vector` 是 1534 份 ——// 因为 vector 能按下标取、能遍历,stack 都不能。//// 这一份练的是「括号匹配」:三种括号,看能不能配平。//// ⚠ 输入一行只含 `()` `[]` `{}` 的串。
#include <bits/stdc++.h>using namespace std;
/** 右括号该配哪个左括号 */char matchOpen(char c) { if (c == ')') return '('; if (c == ']') return '['; return '{';}
int main() { ios::sync_with_stdio(false); cin.tie(nullptr);
string s; cin >> s;
// ★ 全部内容就这三行:开一个空的,扫一遍,push_back 进去。 vector<char> st; bool ok = true; string why;
for (char c : s) { if (c == '(' || c == '[' || c == '{') { st.push_back(c); // 左括号进栈 } else { if (st.empty() || st.back() != matchOpen(c)) { // back() 就是栈顶 ok = false; why = string("第 ") + to_string(&c - &s[0] + 1) + " 个字符 '" + c + "' 配不上"; break; } st.pop_back(); // 配上了,弹掉 } }
cout << "串:" << s << "\n\n"; if (!ok) { cout << "不配平:" << why << "\n"; } else if (!st.empty()) { cout << "不配平:还剩 " << st.size() << " 个左括号没人配\n"; } else { cout << "配平 ✓\n"; } return 0;}点「运行 ▶」看结果
- 开一个当栈用:
vector<char> st; - 扫一遍:
for (char c : s)。 - 是左括号(
([{)就st.push_back(c);。 - ★ 是右括号就看
st.back()—— 那就是栈顶:它空着、或者和这个右括号配不上,就不配平;配上了就st.pop_back();。 - 扫完再看
st.empty():不空说明还有左括号没人配。 - ⚠ 一个小坑:判断「配不配得上」别写成
st.back() == c—— 右括号和左括号本来就是两个不同的字符,要先想「这个右括号该配哪个左括号」,用一个函数查。
★★ vector<int> st; + push_back / back / pop_back 就是栈。本书里 stack<int> 只出现在 4 份代码,而 vector 有 1534 份 —— 因为 vector 还能按下标取、还能遍历,stack 都不能。
用到的写法:vector<int> a; a.push_back(x) a.back() / a.front() a.pop_back() a.empty()
练习(2 道)
只给题面、样例和一句提示,答案是折叠着的 —— 先自己写一版。
练习 ① 按下标遍历:求最大值和它第一次出现的位置
给 n 个整数,求最大值,以及它第一次出现在第几个(从 1 数)。
★ 关键是分清两种遍历:这里必须用下标,因为要的是「第几个」。
n。第二行 n 个整数。输入
6 10 30 20 30 50 20
输出
a = 10 30 20 30 50 20 最大值 50,第一次出现在下标 4(第 5 个) 只用范围 for 求最大值: 50 (对,但不知道位置)
⚠ 先想清楚两件事:for (int x : a) 和 for (int i = 0; ...; i++) 一个拿得到下标、一个拿不到。还有「最大值有多个时取第一个」怎么写。
★★ 要「第几个」就用下标遍历,只要素值就用范围 for。⚠ 判断「第一个」写 > 不写 >= —— 写 >= 会拿到最后一个。
用到的写法:a[i] for (int i = 0; i < (int)a.size(); i++) for (int x : a)
练习 ② 二维 vector:矩阵转置
给一个 n × n 的整数表,把它沿主对角线转置((i, j) 和 (j, i) 交换),输出转置后的表。
★ 动态规划的二维表全是 vector<vector<int>> 这么写的 —— 这一题练的就是它。
n。之后 n 行,每行 n 个整数。输入
4 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
输出
原来的表: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 转置之后: 1 5 9 13 2 6 10 14 3 7 11 15 4 8 12 16
⚠ 三件事:① 二维 vector 那一行怎么写;② 循环怎么只扫上三角(不然就转两次又转回去了);③ i == j 那些格子要跳过。
★★ vector<vector<int>> g(R, vector<int>(C, 0)); —— 内层的 (C, 0) 不能省。省了就是 R 个空 vector,g[i][j] 全是垃圾值。
用到的写法:vector<vector<int>> g(R, vector<int>(C, 0)); a[i] swap(a[i], a[j])
← 上一块:前置知识:比较器和「把函数当参数传」 · 回 STL 专题目录 · 下一块:string:读一整行、切词、找子串 →
⚠ 本页说的是 STL 怎么用。算法本身(Dijkstra、单调栈、状压……)在53 章正课里,而每一章末尾都会告诉你它用到了哪些 STL。