前面几篇讲了 C++ 的语法和数据结构,已经够你看懂大部分代码了。这篇补充一些在刷算法题时经常会用到的实用技巧,都是一些零散但重要的知识点。
ACM 模式 I/O 模板
cin/cout 基本模板
很多 OJ 平台(包括本站)使用 ACM 模式,需要你自己处理输入输出。最常见的模式是先读取数据量 n,然后循环读取 n 组数据:
cin >> a >> b 可以连续读取多个变量,自动跳过空格和换行,非常方便。
另一种常见模式是不知道有多少行输入,用 while (cin >> ...) 循环读取直到输入结束:
int a, b;
while (cin >> a >> b) {
cout << a + b << endl;
}如果需要读取一整行(包括空格),用 getline:
string line;
getline(cin, line);快读优化
cin/cout 默认有一些同步机制,导致速度比较慢。在竞赛和大数据量的题目中,加上下面两行代码可以大幅提升 I/O 速度:
ios::sync_with_stdio(false) 关闭了 cin/cout 和 scanf/printf 之间的同步,cin.tie(nullptr) 解除了 cin 和 cout 之间的绑定。加了这两行之后,cin/cout 的速度接近 scanf/printf。
注意:开了快读优化后,不要再混用
cin和scanf,否则可能读到错误的数据。另外建议用"\n"代替endl,因为endl会强制刷新缓冲区,大量输出时会拖慢速度。
建议养成习惯:做算法题时,main 函数的前两行永远写上这两句,有备无患。
排序
sort + lambda 自定义排序
C++ 的 sort 函数在 <algorithm> 头文件中,可以直接对数组和 vector 排序。默认是升序排列:
比较函数的逻辑是:return a > b 表示如果 a 大于 b,a 排在 b 前面,所以是降序。return a < b 就是升序(和默认行为一样)。
特别注意:C++ 的排序比较器必须返回 bool 值,用
<或>来比较。不要写return a - b;,这是错误的写法,会导致未定义行为。
再看一个按字符串长度排序的例子:
对结构体排序
struct 和 class 类似,用来把多个字段打包成一个类型,刷题中经常用到。自定义结构体的排序也是用 lambda,非常直观:
多级排序
有时候需要按多个条件排序,比如先按分数降序,分数相同再按名字字典序升序:
多级排序的思路很简单:先比较第一个条件,如果能分出高下就直接返回;如果相同,再比较下一个条件。
类型转换
字符串与数字互转
刷题时经常需要在字符串和数字之间转换,C++ 提供了一组方便的函数:
常用的转换函数:
| 函数 | 作用 |
|---|---|
stoi(s) | 字符串 → int |
stoll(s) | 字符串 → long long |
stod(s) | 字符串 → double |
to_string(n) | 数字 → 字符串 |
字符与 ASCII 码
字符本质上就是一个整数(ASCII 码),C++ 中 char 类型可以直接和 int 互转:
字符 - 'a' 这个技巧在算法题中超级常用,比如统计每个字母出现的次数,就可以用一个长度为 26 的数组,用 s[i] - 'a' 作为下标。
常用数学操作
cmath 常用函数
C++ 的数学函数在 <cmath> 头文件中。另外 max、min、swap 这些是 C++ 内置的,在 <algorithm> 中,不需要自己写:
注意:
pow返回double类型,在需要精确整数结果的场景下(比如pow(2, 10)期望得到 1024),由于浮点精度问题可能得到 1023.999...。如果需要整数幂运算,建议自己写循环或者用位运算(1 << 10)。
整数边界值
int 和 long long 都有范围限制,超出范围会溢出。C++ 在 <climits> 中定义了边界常量(<iostream> 通常已经包含了):
溢出防范
算法题中最常见的溢出场景是两个 int 相加或相乘,结果超出 int 范围。解决方法是把其中一个操作数转成 long long:
int a = 100000, b = 100000;
// 错误:int * int 结果还是 int,溢出了
int wrong = a * b;
// 正确:先转 long long 再乘
long long right = (long long)a * b;还有一个常见的坑是 INT_MIN 取绝对值:
// 危险!INT_MIN 的绝对值超出了 int 范围
int x = INT_MIN;
// abs(x) 的结果是未定义的,因为 2147483648 超出了 int 范围
// 正确做法:先转 long long
long long safe = abs((long long)x);记住一个原则:只要计算过程中可能越界,就提前转成 long long。
Lambda 表达式基础
语法
Lambda 表达式的完整语法是:
[捕获列表](参数列表) -> 返回类型 { 函数体 }返回类型通常可以省略,编译器会自动推导。来看几个例子:
捕获列表是 lambda 和普通函数最大的区别。简单记:
[=]:按值捕获所有外部变量(只读)[&]:按引用捕获所有外部变量(可读写)[x]:只按值捕获变量 x[&x]:只按引用捕获变量 x
在排序、自定义比较中的应用
前面排序那节已经大量使用了 lambda,这里补充一个 priority_queue 自定义比较器的写法:
// 小顶堆:传入 greater<int>
priority_queue<int, vector<int>, greater<int>> minHeap;
// 自定义比较:用 lambda
// 注意 priority_queue 的比较逻辑和 sort 相反
// sort 中 return a < b 是升序
// priority_queue 中 return a < b 是大顶堆(和直觉相反!)
auto cmp = [](pair<int,int>& a, pair<int,int>& b) {
return a.first > b.first; // first 小的优先级高 → 小顶堆
};
priority_queue<pair<int,int>, vector<pair<int,int>>, decltype(cmp)> pq(cmp);lambda 在算法题中用得最多的场景就是 sort 的自定义比较和 priority_queue 的自定义比较器,把前面的例子掌握好就够用了。
常用 STL 算法
<algorithm> 头文件里有很多实用的函数,这里介绍刷题中最常用的几个。
reverse、find、count
accumulate
accumulate 在 <numeric> 头文件中,用来计算容器元素的累加和:
min_element / max_element
快速找到容器中的最小/最大元素:
进制转换
有些算法题会涉及进制相关的问题,了解标准库提供的进制转换函数,就不需要自己手搓转换逻辑了。C++ 可以用 bitset 转二进制,用 stoi 的第三个参数解析其他进制,也可以用流操纵符输出不同进制:
刷题中最常用的是 bitset 转二进制和 stoi(s, nullptr, base) 解析其他进制。__builtin_popcount(n) 可以直接统计二进制中 1 的个数,效率很高。注意用了 oct 或 hex 后要用 dec 切回十进制,否则后续所有输出都会受影响。
小结
这篇介绍的技巧在刷题中会反复用到。ios::sync_with_stdio(false) 和 cin.tie(nullptr) 放在 main 开头加速 I/O,养成习惯。排序用 sort + lambda,return a < b 升序,return a > b 降序,不要用减法。字符串和数字互转用 stoi/stoll/to_string,字符下标用 c - 'a'。计算可能越界时提前转 (long long)。reverse、find、count、accumulate 等 STL 算法不用自己手写,max()、min()、swap() 也是内置的。
学到这里,C++ 刷题所需的基础知识就全部介绍完了。接下来就可以开始刷题了,边刷边查,很快就能熟练起来。
后面两篇是选读内容:多线程编程基础和面向对象与模板。在设计模式的代码中会大量使用,如果你暂时只想刷算法题,可以先跳过。