例题给全套:题面、样例、参考代码、以及一步步的讲解。先自己读题想一遍, 再看代码,最后对着讲解核一遍自己想的对不对。
练习只给题面、样例和一句提示,答案是折叠起来的 —— 请先自己写一版跑通了再展开。写不出来也别直接看答案,先回速查那一组找找。
每份代码都能直接点运行:输入框里已经填好了样例输入, 把它改一改再跑,是这一页最值钱的用法。
先把地基摆一遍
你已经会写 int a[100]; 了。vector 就是同一个东西,只多给你三件事:长度可以是运行时才知道的变量、可以随时往后加、它自己知道自己有多长。除此之外用法一模一样 —— 照样 a[0]、a[i],下标照样从 0 开始。
★ 为什么本书几乎全用它而不用裸数组:一是题目的 n 是读进来的,int a[n]; 在标准 C++ 里其实不合法(GCC 容忍而已);二是 vector<int> a(n); 保证全是 0,而函数里的 int a[100]; 里面是垃圾值。
⚠ 但有一个场合反过来用裸数组:几百万个元素的大数组要开成全局的。写在 main 里的数组放在栈上(一般只有 8 MB),开大了直接段错误;全局数组不占栈,而且自动清零。竞赛代码里 const int N = 100005; int a[N]; 就是这个习惯。
★ 还有一条贯穿全书的:数组越界 C++ 不会报错。 它只是安静地读写了别人的内存,于是程序可能在一个毫不相干的地方崩掉,或者答案莫名其妙 —— 查这类问题的第一招是临时把 a[i] 换成 a.at(i)。
例题(2 道)
每道都给全套:题面 → 样例 → 参考代码 → 一步步的讲解。
例题 ① 读一组数,报四个数
给 n 个整数,请依次输出它们的最大值、最小值、总和,再把它们倒序打印一行。
n(1 ≤ n ≤ 10⁵);第二行 n 个整数 aᵢ(|aᵢ| ≤ 10⁹)。n 个整数,倒序,用空格隔开,行末不要有空格。输入
5 3 1 4 1 5
输出
5 1 14 5 1 4 1 3
3 1 4 1 5 的最大 5、最小 1、和 14,倒过来是 5 1 4 1 3。这道题的形状就是本书每道题的开头。唯一要想一下的是:n 是读进来的,那数组该怎么开?
// 例题 ①「读一组数,报四个数」—— 数组与 vector 组//// 本书每道题的开头几乎都长这样:先读 n,再开一个长度 n 的 vector,然后读进去。// 注意 n 是**读进来的**,普通数组写 int a[n]; 在标准 C++ 里其实不合法 —— 这正是用 vector 的理由。
#include <bits/stdc++.h>using namespace std;
int main() { int n; cin >> n;
vector<int> a(n); // 长度 n,全部初始化成 0 for (int i = 0; i < n; i++) cin >> a[i];
int mx = a[0], mn = a[0]; long long sum = 0; for (int x : a) { // 范围 for:把 a 里每个元素依次取出来叫 x mx = max(mx, x); mn = min(mn, x); sum += x; } cout << mx << ' ' << mn << ' ' << sum << "\n";
// 倒序输出:要用到下标,就老老实实写普通 for for (int i = n - 1; i >= 0; i--) { cout << a[i]; if (i > 0) cout << ' '; // 行末不留空格 } cout << "\n"; return 0;}点「运行 ▶」看结果
vector<int> a(n);—— 读成「一个装 int 的 vector,名字叫 a,长度 n」。尖括号里写装什么,圆括号里写装多少个。它保证每一格都是 0。- ⚠ 为什么不写
int a[n];:n是运行时才知道的,这种写法在标准 C++ 里其实不合法(GCC 容忍而已)。而且函数里的普通数组不会自动清零,里面是上一个用这块内存的人留下的垃圾值。 - 只读地扫一遍用范围 for:
for (int x : a),读成「把 a 里每个元素依次取出来叫 x」。⚠x是一份复制品 —— 要改元素得写for (int &x : a)。 - 倒序输出要用到下标 ⇒ 老老实实写
for (int i = n - 1; i >= 0; i--)。⇒ 判据很简单:用得到下标就写普通 for,用不到就写范围 for。 - ⚠ 和最后那个
if (i > 0) cout << ' ';—— 行末不留空格。这不是洁癖:本书第 47 章量过,「行末多一个空格」在 300 轮对拍里被抓了 284 次。
顺带一条:n 个 10⁹ 加起来可以到 10¹⁴ ⇒ sum 必须是 long long,而数组本身用 int 就够。「要不要 long long」永远是按那一次运算问的。
用到的写法:vector<int> a(n) for (int x : a) a.size() / a.push_back(x) / a.back()
例题 ② 矩阵转置
给一个 R 行 C 列的矩阵,请输出它的转置 —— 也就是把行和列对调之后的那个 C 行 R 列的矩阵。
说人话:原来第 i 行第 j 列的那个数,转置之后跑到第 j 行第 i 列。
R、C(1 ≤ R, C ≤ 100);接下来 R 行,每行 C 个整数。C 行,每行 R 个整数,用空格隔开,行末不要有空格。输入
2 3 1 2 3 4 5 6
输出
1 4 2 5 3 6
二维 vector 的括号里有两个数字,而它们不能写反。先想清楚:vector<vector<int>> f(R, vector<int>(C)) 里哪个是行数?
// 例题 ②「矩阵转置」—— 数组与 vector 组//// 二维 vector 从里往外读:vector<int>(C) 是「一行,C 个格子」,// 外面那层是「R 个这样的行」。⇒ 第一个数永远是**行数**。// ⚠ 行列写反了程序照样能跑,只是答案错 —— 而小数据常常正好是方阵,看不出来。
#include <bits/stdc++.h>using namespace std;
int main() { int R, C; cin >> R >> C;
vector<vector<int>> a(R, vector<int>(C)); // R 行 C 列,全 0 for (int i = 0; i < R; i++) for (int j = 0; j < C; j++) cin >> a[i][j];
// 转置之后是 C 行 R 列 —— 新表的两个维度正好换过来 vector<vector<int>> b(C, vector<int>(R)); for (int i = 0; i < R; i++) for (int j = 0; j < C; j++) b[j][i] = a[i][j];
for (int i = 0; i < C; i++) for (int j = 0; j < R; j++) cout << b[i][j] << (j + 1 == R ? '\n' : ' '); return 0;}点「运行 ▶」看结果
- 从里往外读这一句:
vector<int>(C)是「一行,C 个格子」;外面那层vector<vector<int>> a(R, …)是「R 个这样的行」。⇒ 第一个数永远是行数。 取元素照样写a[i][j]。 - ⚠ 行列写反了程序照样跑,只是答案错 —— 而且小数据常常正好是方阵,一测就过。这是本书专门记下来的一个坑。
- 转置之后的表要开成
vector<vector<int>> b(C, vector<int>(R))——两个维度跟着换过来。这一步写错就会越界。 - 搬数据只有一句:
b[j][i] = a[i][j];—— 把定义原样翻译过来就行。
二维 vector 是第 21 章之后每一章的主角(DP 的表格、网格图、邻接矩阵全是它)。⇒ 这一页要留下的肌肉记忆只有一条:第一个数是行数。
用到的写法:vector<vector<int>> f(R, vector<int>(C, 0)) vector<int> a(n)
练习(3 道)
答案折叠着,先自己写一版。
练习 ① 每个数出现了几次
给 n 个整数(都在 0 ~ 100 之间),统计每个值各出现了多少次,按值从小到大输出所有出现过的值及其次数。
n(1 ≤ n ≤ 10⁵);第二行 n 个整数 aᵢ(0 ≤ aᵢ ≤ 100)。输入
6 3 1 3 2 1 3
输出
1 2 2 1 3 3
值域只有 0~100 ⇒ 可以开一个数组,用值本身当下标。⚠ 那个数组要开多长?(想清楚 a 能取到的最大值。)
这一招叫「桶」,贯穿全书:第 6 章的计数、第 47 章的 cnt[26]、第 38 章离散化的前身都是它。⇒ 而它的前提是值域不能太大 —— 值域到 10⁹ 时开不出这个数组,那时候才轮到 map。
练习 ② 读到 0 为止
不断读入整数,直到读到一个 0 为止(这个 0 不算在内)。请输出一共读到了几个数,以及最后一个数是多少。
⚠ 如果一个数都没读到(第一个就是 0),只输出一行 0。
输入
4 8 15 0
输出
3 15
4 8 15 三个数就遇到了 0,最后一个是 15。事先不知道有多少个数 ⇒ 开不出固定长度的数组。vector 比普通数组多出来的第二件本事正好派上用场。
练习 ③ 行和与列和
给一个 R 行 C 列的矩阵,输出每一行的和,再输出每一列的和。
R、C(1 ≤ R, C ≤ 500);接下来 R 行,每行 C 个整数(|aᵢⱼ| ≤ 10⁹)。R 个整数(各行的和);第二行 C 个整数(各列的和)。均用空格隔开,行末不留空格。输入
2 3 1 2 3 4 5 6
输出
6 15 5 7 9
不用先把整个矩阵存下来 —— 边读边累加就行。⚠ 两个累加器数组的长度不一样,写反了就是越界。
⚠ 还有一件事:500 × 500 个 10⁹ 加起来是 2.5 × 10¹⁴ ⇒ 累加器得是 long long。
用到的写法:vector<int> a(n, 7) vector<vector<int>> f(R, vector<int>(C, 0))