2025.6.1 讲课文件
2.2.1 基础知识与编程环境
在信息学竞赛中,我们通常在Linux环境下进行编程、编译和测试。熟悉Linux环境能极大提高我们的效率。
1. 【5】Linux终端中常用的文件与目录操作命令
Linux的灵魂在于命令行终端。下面是一些最常用的命令:
ls** (list files)**:列出当前目录下的文件和文件夹。ls: 列出当前目录内容。ls -l: 列出详细信息(权限、所有者、大小、修改日期等)。ls -a: 列出所有文件,包括隐藏文件(以.开头)。ls -lh:-l的详细信息,但文件大小以K, M, G等易读格式显示。- 示例:
ls -l /home/student/
cd** (change directory)**:切换当前工作目录。cd dirname: 进入名为dirname的子目录。cd ..: 返回上一级目录。cd ~或cd: 返回用户主目录 (e.g.,/home/student/)。cd /: 进入根目录。- 示例:
cd /home/student/projects
pwd** (print working directory)**:显示当前所在的完整路径。- 示例:
pwd(可能会输出/home/student/projects)
- 示例:
mkdir** (make directory)**:创建新目录。mkdir newdir: 在当前目录下创建名为newdir的目录。mkdir -p path/to/newdir: 递归创建目录,如果父目录不存在也会一并创建。- 示例:
mkdir my_code
rm** (remove)**:删除文件或目录。rm filename: 删除文件。rm -r dirname: 递归删除目录及其下所有内容 (非常危险,请谨慎使用!)。rm -f filename: 强制删除,不提示 (更危险!)。rm -rf dirname: 强制递归删除目录 (极度危险!)。- 示例:
rm temp.txt(删除文件),rm -r old_project(删除目录)
cp** (copy)**:复制文件或目录。cp source_file destination_file: 复制文件。cp source_file destination_directory/: 将文件复制到指定目录。cp -r source_directory destination_directory/: 递归复制目录。- 示例:
cp main.cpp main_backup.cpp,cp -r projectA /backup/
mv** (move)**:移动文件或目录,也可用于重命名。mv source_file destination_directory/: 移动文件到目录。mv old_filename new_filename: 重命名文件。mv source_directory destination_directory/: 移动目录。- 示例:
mv temp.cpp problem1.cpp(重命名),mv problem1.cpp solutions/(移动)
2. 【5】Linux系统下常见文本编辑工具的使用
编写代码需要文本编辑器。Linux下有多种选择:
- Vim/Vi: 功能强大,学习曲线较陡,但熟练后效率极高。是竞赛选手常用的编辑器之一。
- 基本模式:
vim filename.cpp: 打开或创建文件。- 命令模式 (Normal Mode): 默认模式,用于移动光标、删除、复制、粘贴等。
i: 切换到插入模式 (Insert Mode),在光标前开始输入。a: 切换到插入模式,在光标后开始输入。o: 在当前行下方新建一行并进入插入模式。x: 删除光标处的字符。dd: 删除当前行。yy: 复制当前行。p: 粘贴。Esc: 从插入模式或其他模式返回命令模式。
- 底行命令模式 (Command-line Mode): 在命令模式下按
:进入。:w: 保存文件。:q: 退出Vim。:wq或:x: 保存并退出。:q!: 强制退出,不保存修改。:set nu: 显示行号。
- 示例:
vim hello.cpp- 按
i进入插入模式。 - 输入你的C++代码。
- 按
Esc返回命令模式。 - 输入
:wq保存并退出。
- 基本模式:
- Nano: 简单易用,适合初学者。
nano filename.cpp: 打开或创建文件。- 常用快捷键会显示在屏幕底部 (e.g.,
^O代表Ctrl+O保存,^X代表Ctrl+X退出)。
- Gedit: 图形界面编辑器,类似Windows的记事本但功能更强。
gedit filename.cpp &: 在终端打开Gedit,&让它在后台运行,不阻塞终端。
3. 【5】常用编译命令 g++ 与相关编译选项
g++ 是GCC (GNU Compiler Collection) 中的C++编译器。
- 基本编译:
g++ source.cpp -o executable_namesource.cpp: 你的源代码文件名。-o executable_name: 指定输出的可执行文件名。如果省略,默认为a.out。- 示例:
g++ main.cpp -o main_program
- 常用编译选项:
-std=c++11(或-std=c++14,-std=c++17,-std=c++20): 指定C++标准。NOIP通常使用C++11或C++14。- 示例:
g++ -std=c++14 main.cpp -o main_program
- 示例:
-O2: 开启二级优化。这是竞赛中最常用的优化级别,能显著提升程序运行速度。- 示例:
g++ -std=c++14 -O2 main.cpp -o main_program
- 示例:
-Wall: 显示所有警告信息。有助于发现潜在错误。- 示例:
g++ -std=c++14 -Wall main.cpp -o main_program
- 示例:
-g: 生成调试信息,供GDB调试器使用。编译调试版时使用,提交最终代码时通常不加(或用-O2覆盖)。- 示例:
g++ -std=c++14 -g main.cpp -o debug_program
- 示例:
-DONLINE_JUDGE: 在一些评测系统中,会定义这个宏。你可以用它来区分本地调试和评测环境(例如,本地读写文件,评测时标准输入输出)。
#ifndef ONLINE_JUDGE
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
#endif
* 编译时可加:`g++ -DONLINE_JUDGE main.cpp -o main_program`
- `"-Wl,--stack=536870912"`: 防爆栈,栈空间开到512MB。
- 推荐竞赛编译命令:
g++ -std=c++14 -O2 -Wall "-Wl,--stack=536870912" main.cpp -o main_program
4. 【5】在Linux系统终端中运行程序,使用 time 命令查看程序用时
- 运行程序:
- 如果可执行文件在当前目录,使用
./executable_name - 示例:
./main_program
- 如果可执行文件在当前目录,使用
time** 命令**:- 用于测量程序运行消耗的时间。
time ./executable_name- 输出通常包含三项时间:
real: 程序从开始到结束的真实时间(墙上时钟时间),包括IO等待、其他进程占用CPU等。user: 程序在用户态消耗的CPU时间。sys: 程序在内核态消耗的CPU时间。
- 竞赛中关注
user+sys时间,这代表了CPU实际执行你的代码所花的时间。 - 示例:
time ./main_program
5. 【5】调试工具 GDB 的使用
GDB (GNU Debugger) 是强大的命令行调试工具。
- 编译以供调试: 必须使用
-g选项编译。g++ -std=c++14 -g my_buggy_code.cpp -o my_buggy_program
- 启动GDB:
gdb ./my_buggy_program
- 常用GDB命令:
run(或r): 开始运行程序。可以带参数,如run < input.txt。break <location>(或b): 设置断点。b main: 在main函数入口设置断点。b filename.cpp:line_number: 在指定文件的指定行设置断点。b function_name: 在指定函数入口设置断点。- 示例:
b my_buggy_code.cpp:25
info breakpoints(或i b): 查看已设置的断点。delete <breakpoint_number>(或d): 删除断点。next(或n): 执行下一行代码(如果当前行是函数调用,则执行整个函数,不进入函数内部)。step(或s): 执行下一行代码(如果当前行是函数调用,则进入函数内部)。continue(或c): 继续运行程序直到下一个断点或程序结束。print <expression>(或p): 打印变量或表达式的值。- 示例:
p i,p arr[i],p my_struct.member
- 示例:
watch <expression>: 设置观察点。当表达式的值改变时,程序会暂停。bt(backtrace): 查看函数调用栈。当程序崩溃或在断点处时,非常有用。quit(或q): 退出GDB。
- GDB调试流程示例:
g++ -g my_program.cpp -o my_programgdb ./my_program(gdb) b main(在main函数设断点)(gdb) r(运行程序,会在main处停下)(gdb) b 15(假设第15行有问题,设断点)(gdb) c(继续运行到第15行)(gdb) p var_name(查看变量var_name的值)(gdb) n(单步执行)- ... (重复查看变量、单步执行,直到找到问题)
(gdb) q(退出)
练习题 (环境部分):
- 创建一个名为
noip_review的目录。 - 进入
noip_review目录,创建一个名为test.cpp的C++文件,内容为打印 "Hello NOIP!"。 - 使用
g++编译test.cpp,使用C++14标准,开启O2优化和所有警告,输出可执行文件名为hello。 - 运行
hello程序。 - 使用
time命令测量hello程序的运行时间。 - (选做) 在
test.cpp中故意引入一个简单的逻辑错误 (如循环条件错误导致死循环或越界),用g++ -g编译,并尝试用gdb找到错误。
2.2.2 C++ 程序设计
1. 类 (class)
面向对象编程 (OOP) 的核心概念之一。类是创建对象的蓝图或模板。
- 【6】类的概念及简单应用
- 概念: 类是一种用户自定义的数据类型,它封装了数据(成员变量/属性)和操作这些数据的方法(成员函数/行为)。
- 封装 (Encapsulation): 将数据和操作数据的函数捆绑在一起,对外部隐藏对象的内部实现细节。
- 访问修饰符:
public: 成员可以被类外部任何代码访问。private: 成员只能被类自身的成员函数访问(默认)。protected: 成员可以被类自身的成员函数以及派生类的成员函数访问。
- 对象 (Object): 类的实例。
- 构造函数 (Constructor): 特殊的成员函数,在创建对象时自动调用,用于初始化对象。函数名与类名相同,无返回类型。
- 析构函数 (Destructor): 特殊的成员函数,在对象销毁时自动调用,用于释放资源。函数名是
~加上类名,无参数无返回类型。 - 简单应用示例 (Point类):
#include <iostream>
#include <cmath> // For sqrt
class Point {
private: // 数据成员通常设为私有,体现封装
double x;
double y;
public:
// 构造函数 (无参)
Point() : x(0.0), y(0.0) {
std::cout << "Default Point constructor called." << std::endl;
}
// 构造函数 (带参)
Point(double x_val, double y_val) : x(x_val), y(y_val) {
std::cout << "Parameterized Point constructor called for (" << x << ", " << y << ")" << std::endl;
}
// 成员函数:获取x坐标
double getX() const { // const表示此函数不会修改对象的数据成员
return x;
}
// 成员函数:获取y坐标
double getY() const {
return y;
}
// 成员函数:设置坐标
void setCoordinates(double new_x, double new_y) {
x = new_x;
y = new_y;
}
// 成员函数:计算到原点的距离
double distanceToOrigin() const {
return std::sqrt(x * x + y * y);
}
// 析构函数
~Point() {
std::cout << "Point destructor called for (" << x << ", " << y << ")" << std::endl;
}
}; // 注意类定义末尾的分号
int main() {
Point p1; // 调用默认构造函数,p1是(0,0)
Point p2(3.0, 4.0); // 调用带参构造函数,p2是(3,4)
std::cout << "P1: (" << p1.getX() << ", " << p1.getY() << ")" << std::endl;
std::cout << "P2: (" << p2.getX() << ", " << p2.getY() << ")" << std::endl;
p1.setCoordinates(1.0, 1.0);
std::cout << "P1 after setCoordinates: (" << p1.getX() << ", " << p1.getY() << ")" << std::endl;
std::cout << "Distance of P2 from origin: " << p2.distanceToOrigin() << std::endl;
return 0; // p1和p2在此处销毁,自动调用析构函数
}
- 【6】成员函数和运算符重载
- 成员函数: 定义在类内部或外部(需用类名作用域解析符
::)的函数,用于操作类的数据成员。const成员函数: 在函数声明末尾加const,表示该函数不会修改对象的任何数据成员。getX()和getY()就是例子。
- 运算符重载 (Operator Overloading): 允许为自定义类型(类)的运算赋予特定含义。使得类对象可以像内置类型一样使用运算符。
- 语法:
return_type operator symbol (parameters) - 可以作为成员函数或全局函数(通常配合友元
friend)。 - 不能重载的运算符:
.(成员访问),.*(成员指针访问),::(作用域解析),?:(条件运算符),sizeof。
- 语法:
- 运算符重载示例 (为Point类重载
+和<<):
- 成员函数: 定义在类内部或外部(需用类名作用域解析符
#include <iostream>
#include <cmath>
class Point {
private:
double x;
double y;
public:
Point(double x_val = 0.0, double y_val = 0.0) : x(x_val), y(y_val) {}
double getX() const { return x; }
double getY() const { return y; }
// 成员函数形式重载 + 运算符
// p1 + p2 等价于 p1.operator+(p2)
Point operator+(const Point& other) const {
return Point(this->x + other.x, this->y + other.y);
}
// 友元函数:允许非成员函数访问类的私有成员
// 用于重载 << (输出流运算符)
friend std::ostream& operator<<(std::ostream& os, const Point& p);
};
// 全局函数形式重载 << 运算符 (通常作为友元)
// cout << p 等价于 operator<<(cout, p)
std::ostream& operator<<(std::ostream& os, const Point& p) {
os << "(" << p.x << ", " << p.y << ")"; // 可以访问p.x, p.y因为是友元
return os;
}
int main() {
Point p1(1.0, 2.0);
Point p2(3.0, 4.0);
Point p3 = p1 + p2; // 使用重载的 +
// Point p3 = p1.operator+(p2); // 等效调用
std::cout << "P1: " << p1 << std::endl; // 使用重载的 <<
std::cout << "P2: " << p2 << std::endl;
std::cout << "P3 (P1 + P2): " << p3 << std::endl;
return 0;
}
- **常见的重载**: `+`, `-`, `*`, `/`, `==`, `!=`, `<`, `>`, `<<`, `>>`, `[]` (下标), `()` (函数调用)。
- 在竞赛中,结构体(`struct`,默认成员为`public`的类)常用于自定义数据类型,并为其重载比较运算符(如 `<`),以便用于 `std::sort` 或 `std::priority_queue` 等。
练习题 (类部分):
- 设计一个
Rectangle类,包含私有成员width和height(浮点数)。 - 为
Rectangle类编写构造函数,可以初始化宽度和高度。 - 编写成员函数
getArea()计算并返回矩形面积。 - 编写成员函数
getPerimeter()计算并返回矩形周长。 - (选做) 重载
Rectangle类的+运算符,使其返回一个新的Rectangle对象,其宽度为两者宽度之和,高度为两者高度之和。 - (选做) 重载
Rectangle类的<<运算符,使其能友好地输出矩形信息,例如Rectangle(width=5, height=10)。
2. STL 模板 (Standard Template Library)
STL是C++标准库的重要组成部分,提供了大量预先实现好的数据结构和算法,极大提高了编程效率和代码质量。
- 【5】容器 (container) 和迭代器 (iterator)
- 容器: 用来存储和管理数据集合的对象。
- 序列容器 (Sequence Containers): 元素按线性顺序排列。
vector: 动态数组。尾部插入/删除O(1)(均摊),中间/头部插入/删除O(N)。随机访问O(1)。deque: 双端队列。头部/尾部插入/删除O(1)。随机访问O(1)(但比vector慢)。list: 双向链表。任意位置插入/删除O(1)(需有迭代器指向该位置)。不支持随机访问,访问O(N)。
- 关联容器 (Associative Containers): 元素自动排序(基于键)。
set: 存储唯一元素的有序集合。基于红黑树,插入、删除、查找O(log N)。multiset: 存储可重复元素的有序集合。操作同set。map: 存储键值对 (key-value pair) 的有序集合,键唯一。基于红黑树,插入、删除、查找(按键)O(log N)。multimap: 存储键值对的可重复有序集合。操作同map。
- 无序关联容器 (Unordered Associative Containers) (C++11起): 元素无特定顺序,基于哈希表。
unordered_set,unordered_multiset,unordered_map,unordered_multimap。- 平均情况下插入、删除、查找O(1),最坏O(N)。竞赛中若不卡哈希冲突,通常比有序版本快。
- 容器适配器 (Container Adapters): 基于其他容器实现特定接口。
stack: 栈 (LIFO)。基于deque(默认),vector, 或list。queue: 队列 (FIFO)。基于deque(默认) 或list。priority_queue: 优先队列。基于vector和堆结构。
- 序列容器 (Sequence Containers): 元素按线性顺序排列。
- 迭代器 (Iterator): 类似指针的对象,用于遍历容器中的元素。提供对容器元素的访问方法。
- 种类: 输入迭代器、输出迭代器、前向迭代器、双向迭代器、随机访问迭代器。不同容器支持不同类型的迭代器。
- 常用操作:
container.begin(): 返回指向第一个元素的迭代器。container.end(): 返回指向最后一个元素之后位置的迭代器(哨兵)。*it: 解引用,获取迭代器指向的元素。it++/++it: 移动到下一个元素。it--/--it: (双向或随机访问迭代器) 移动到上一个元素。it1 == it2,it1 != it2: 比较迭代器。
- 遍历示例 (vector):
- 容器: 用来存储和管理数据集合的对象。
#include <vector>
#include <iostream>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
// 使用迭代器遍历
for (std::vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// C++11 范围for循环 (range-based for loop),更简洁
for (int x : v) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
- 【5】对 (pair)、元组 (tuple)
std::pair<T1, T2>(头文件<utility>)- 存储两个异构或同构类型的值。
- 成员:
first(类型T1),second(类型T2)。 - 构造:
std::pair<int, double> p1(10, 3.14); - 辅助函数:
std::make_pair(val1, val2)自动推导类型。 - 比较:
pair默认按first成员比较,若first相等则比较second成员。 - 示例:
#include <utility> // for std::pair, std::make_pair
#include <iostream>
#include <string>
#include <vector>
#include <algorithm> // for std::sort
int main() {
std::pair<int, std::string> p1 = std::make_pair(1, "apple");
std::cout << "ID: " << p1.first << ", Name: " << p1.second << std::endl;
std::vector<std::pair<int, std::string>> items;
items.push_back(std::make_pair(3, "banana"));
items.push_back(std::make_pair(1, "orange"));
items.push_back(std::make_pair(2, "apple"));
std::sort(items.begin(), items.end()); // 默认按pair的first成员排序
for (const auto& item : items) {
std::cout << item.first << ": " << item.second << std::endl;
}
// Output will be:
// 1: orange
// 2: apple
// 3: banana
return 0;
}
- `std::tuple<Types...>` (C++11, 头文件 `<tuple>`)
* 存储多个(任意数量)异构或同构类型的值。
* 构造: `std::tuple<int, double, std::string> t1(10, 3.14, "hello");`
* 辅助函数: `std::make_tuple(vals...)`。
* 访问元素: `std::get<index>(tuple_object)` (index是编译期常量)。
* 比较: 类似 `pair`,逐个元素比较。
* **示例**:
#include <tuple> // for std::tuple, std::make_tuple, std::get
#include <iostream>
#include <string>
int main() {
std::tuple<int, double, std::string> t1 = std::make_tuple(25, 98.5, "Alice");
std::cout << "ID: " << std::get<0>(t1) << std::endl;
std::cout << "Score: " << std::get<1>(t1) << std::endl;
std::cout << "Name: " << std::get<2>(t1) << std::endl;
// C++17 结构化绑定 (structured binding)
auto [id, score, name] = t1;
std::cout << "Structured binding: " << id << ", " << score << ", " << name << std::endl;
return 0;
}
- 【5】集合 (set)、多重集合 (multiset) (头文件
<set>)std::set<T>:- 存储唯一的、有序的元素。默认按
<运算符升序排列。 - 基于红黑树实现。
- 常用操作:
insert(val): 插入元素。若元素已存在,则不插入。返回std::pair<iterator, bool>,bool表示是否成功插入。O(log N)。erase(val): 删除值为val的元素。返回删除的元素数量 (0或1)。O(log N)。erase(iterator): 删除迭代器指向的元素。O(log N) 或 O(1) (均摊,C++11后)。find(val): 查找元素。返回指向元素的迭代器,若未找到则返回end()。O(log N)。count(val): 返回值为val的元素数量 (0或1)。O(log N)。size(): 元素数量。O(1)。empty(): 是否为空。O(1)。lower_bound(val): 返回指向第一个不小于val的元素的迭代器。O(log N)。upper_bound(val): 返回指向第一个大于val的元素的迭代器。O(log N)。
- 存储唯一的、有序的元素。默认按
std::multiset<T>:- 存储有序的元素,允许重复。
- 操作与
set类似,但insert总是成功,count可能返回大于1的值。erase(val)会删除所有值为val的元素。
- 示例 (set):
#include <set>
#include <iostream>
int main() {
std::set<int> s;
s.insert(10);
s.insert(5);
s.insert(20);
s.insert(5); // 5已存在,不会重复插入
std::cout << "Set elements: ";
for (int x : s) { // 自动有序遍历
std::cout << x << " "; // Output: 5 10 20
}
std::cout << std::endl;
if (s.count(10)) {
std::cout << "10 is in the set." << std::endl;
}
s.erase(10);
auto it = s.find(20);
if (it != s.end()) {
std::cout << "Found 20." << std::endl;
}
return 0;
}
- 【5】双端队列 (deque)、优先队列 (priority_queue)
std::deque<T>(Double-Ended Queue, 头文件<deque>)- 序列容器,允许在两端快速插入和删除。
- 内部实现通常是分块的动态数组,支持随机访问 (O(1)但比vector慢)。
- 常用操作:
push_front(val): 头部插入。O(1)。pop_front(): 头部删除。O(1)。push_back(val): 尾部插入。O(1)。pop_back(): 尾部删除。O(1)。front(): 访问头部元素。back(): 访问尾部元素。[],at(): 随机访问。size(),empty().
- 示例:
#include <deque>
#include <iostream>
int main() {
std::deque<int> dq;
dq.push_back(10); // dq: {10}
dq.push_front(5); // dq: {5, 10}
dq.push_back(20); // dq: {5, 10, 20}
dq.push_front(1); // dq: {1, 5, 10, 20}
std::cout << "Front: " << dq.front() << ", Back: " << dq.back() << std::endl;
dq.pop_front(); // dq: {5, 10, 20}
dq.pop_back(); // dq: {5, 10}
std::cout << "Deque elements: ";
for (int x : dq) {
std::cout << x << " ";
}
std::cout << std::endl;
return 0;
}
- `std::priority_queue<T, Container=std::vector<T>, Compare=std::less<T>>` (头文件 `<queue>`)
* 容器适配器,元素按优先级出队。内部用堆 (heap) 实现。
* 默认是最大堆 (largest element has highest priority, `top()`返回最大值)。
* `T`: 元素类型。
* `Container`: 底层容器类型,必须支持随机访问迭代器和`front()`, `push_back()`, `pop_back()`,如 `std::vector` (默认) 或 `std::deque`。
* `Compare`: 比较函数对象类型。`std::less<T>` 导致最大堆,`std::greater<T>` 导致最小堆。
* **常用操作**:
+ `push(val)`: 插入元素。O(log N)。
+ `pop()`: 移除队首元素 (优先级最高的)。O(log N)。
+ `top()`: 访问队首元素。O(1)。
+ `size()`, `empty()`.
* **示例 (最大堆和最小堆)**:
#include <queue> // For std::priority_queue
#include <vector>
#include <iostream>
#include <functional> // For std::greater
int main() {
// 最大堆 (默认)
std::priority_queue<int> max_pq;
max_pq.push(10);
max_pq.push(5);
max_pq.push(20);
std::cout << "Max heap top: " << max_pq.top() << std::endl; // 20
max_pq.pop();
std::cout << "Max heap top after pop: " << max_pq.top() << std::endl; // 10
// 最小堆
std::priority_queue<int, std::vector<int>, std::greater<int>> min_pq;
min_pq.push(10);
min_pq.push(5);
min_pq.push(20);
std::cout << "Min heap top: " << min_pq.top() << std::endl; // 5
min_pq.pop();
std::cout << "Min heap top after pop: " << min_pq.top() << std::endl; // 10
// 对于自定义结构体,需要重载 < 运算符或提供自定义比较函数
struct Task {
int priority;
std::string name;
// For max_pq (std::less is default, compares using <)
// We want higher priority value to be "less" so it comes first in a max-heap sense.
// Or, more simply, for a max-heap based on 'priority', if a.priority < b.priority, a comes after b.
bool operator<(const Task& other) const {
return priority < other.priority; // Higher priority value means "greater"
}
};
std::priority_queue<Task> task_pq;
task_pq.push({3, "High"});
task_pq.push({1, "Low"});
task_pq.push({2, "Medium"});
std::cout << "Highest priority task: " << task_pq.top().name << " (Prio: " << task_pq.top().priority << ")" << std::endl; // High
return 0;
}
注意:对于自定义类型,std::priority_queue默认使用 operator<。如果 a < b 为 true,则 a 的优先级低于 b。所以,如果想让数值大的优先级高,operator< 要正常定义(小的数值 "小于" 大的数值)。
- 【5】映射 (map)、多重映射 (multimap) (头文件
<map>)std::map<Key, Value>:- 存储键值对 (
std::pair<const Key, Value>),键Key唯一且有序。 - 基于红黑树实现。
- 常用操作:
insert(std::make_pair(key, value))或insert({key, value})(C++11): 插入键值对。O(log N)。operator[](key): 访问键key对应的value。如果key不存在:- 对于查询:会插入一个新元素,
key为给定键,value为默认构造的值,然后返回对该value的引用。 - 对于赋值
map_obj[key] = val: 若key不存在,则插入;若存在,则修改。 - 注意: 仅查询时使用
[]可能意外插入元素。建议用find或at(C++11) 进行安全查询。
- 对于查询:会插入一个新元素,
at(key)(C++11): 访问键key对应的value。如果key不存在,抛出std::out_of_range异常。erase(key): 删除键为key的元素。O(log N)。erase(iterator): 删除迭代器指向的元素。O(log N) 或 O(1) (均摊,C++11后)。find(key): 查找键。返回指向元素的迭代器,若未找到则返回end()。O(log N)。count(key): 返回键为key的元素数量 (0或1)。O(log N)。size(),empty().lower_bound(key),upper_bound(key).
- 存储键值对 (
std::multimap<Key, Value>:- 存储键值对,允许键重复,键有序。
- 操作与
map类似,但operator[]和at()不可用(因为键可能不唯一)。count(key)可能返回大于1的值。find(key)返回第一个匹配的迭代器。equal_range(key)返回一个pairof iterators,表示所有键为key的元素范围。
- 示例 (map):
#include <map>
#include <iostream>
#include <string>
int main() {
std::map<std::string, int> ages;
ages["Alice"] = 30; // Insert or update
ages["Bob"] = 25;
ages.insert(std::make_pair("Charlie", 35));
ages.insert({"David", 28}); // C++11 initializer list
std::cout << "Alice's age: " << ages["Alice"] << std::endl;
// Iterate (map stores pairs, elements are sorted by key)
std::cout << "All ages:" << std::endl;
for (const auto& pair : ages) { // auto& pair is std::pair<const std::string, int>&
std::cout << pair.first << ": " << pair.second << std::endl;
}
std::string name_to_find = "Bob";
auto it = ages.find(name_to_find);
if (it != ages.end()) {
std::cout << name_to_find << " found, age: " << it->second << std::endl;
} else {
std::cout << name_to_find << " not found." << std::endl;
}
ages.erase("Alice");
std::cout << "Count of Alice: " << ages.count("Alice") << std::endl; // 0
return 0;
}
- 【5】位集合 (bitset) (头文件
<bitset>)- 固定大小的位序列 (0s 和 1s)。大小在编译时确定。
- 空间优化,每个布尔值只占1位。
- 支持位运算。
- 声明:
std::bitset<N> b;(N是位数,必须是编译期常量) - 常用操作:
- 构造:
std::bitset<8> b1;(全0),std::bitset<8> b2(10);(00001010),std::bitset<8> b3("101010");(00101010) operator[]: 访问单个位 (返回特殊代理对象,可读写)。count(): 返回1的个数。O(N) 或更快 (popcount指令)。size(): 返回位数N。O(1)。test(pos): 检查某位是否为1 (true/false)。any(): 是否至少有一个1。none(): 是否全为0。all(): 是否全为1 (C++11)。set(): 全部设为1。set(pos, val=true): 设置某位。reset(): 全部设为0。reset(pos): 重置某位为0。flip(): 全部翻转。flip(pos): 翻转某位。- 位运算:
&,|,^,~,<<,>>. to_string(): 转换为字符串。to_ulong(),to_ullong(): 转换为unsigned long / long long (如果位数不超过)。
- 构造:
- 示例:
#include <bitset>
#include <iostream>
#include <string>
int main() {
std::bitset<8> b1("10100101");
std::bitset<8> b2(15); // 00001111
std::cout << "b1: " << b1 << std::endl;
std::cout << "b2: " << b2 << std::endl;
std::cout << "b1[0]: " << b1[0] << ", b1[1]: " << b1[1] << std::endl; // Least significant bit is b1[0]
b1[0] = 0;
std::cout << "b1 after b1[0]=0: " << b1 << std::endl; // 10100100
std::cout << "b1.count(): " << b1.count() << std::endl; // 3
std::bitset<8> b_and = b1 & b2;
std::cout << "b1 & b2: " << b_and << std::endl; // 00000100
std::bitset<8> b_or = b1 | b2;
std::cout << "b1 | b2: " << b_or << std::endl; // 10101111
b1.flip(7); // Flip most significant bit (index 7)
std::cout << "b1 after flip(7): " << b1 << std::endl; // 00100100
if (b1.any()) {
std::cout << "b1 has at least one bit set." << std::endl;
}
return 0;
}
- 【5】算法模板库中的常用函数 (头文件
<algorithm>)- 提供大量通用算法,作用于迭代器指定的范围
[first, last)。 - 排序与搜索:
std::sort(first, last, [comp]): 对范围元素排序。默认升序 (<)。comp是可选比较函数。O(N log N)。std::stable_sort(first, last, [comp]): 稳定排序。O(N log N) 或 O(N log^2 N)。std::partial_sort(first, middle, last, [comp]): 将范围排序,使得[first, middle)包含最小的middle-first个元素。std::binary_search(first, last, val, [comp]): 在有序范围中查找val。返回bool。O(log N)。std::lower_bound(first, last, val, [comp]): 在有序范围中返回第一个不小于val的元素的迭代器。O(log N)。std::upper_bound(first, last, val, [comp]): 在有序范围中返回第一个大于val的元素的迭代器。O(log N)。std::equal_range(first, last, val, [comp]): 返回一个pairof iterators,表示val在有序范围中的等价范围 ([lower_bound,upper_bound))。
- 查找:
std::find(first, last, val): 查找第一个等于val的元素,返回迭代器或last。O(N)。std::find_if(first, last, pred): 查找第一个使一元谓词pred返回true的元素。
- 修改序列:
std::reverse(first, last): 反转范围内的元素。O(N)。std::rotate(first, middle, last): 旋转范围,middle成为新的first。std::unique(first, last, [pred]): 移除有序范围中的连续重复元素(实际是把不重复的移到前面,返回不重复部分的尾后迭代器)。通常先sort再unique。std::fill(first, last, val): 用val填充范围。std::copy(first, last, result_first): 复制范围到另一个位置。std::remove(first, last, val): 移除所有等于val的元素(逻辑移除,返回新尾后迭代器)。std::replace(first, last, old_val, new_val): 替换所有old_val为new_val。
- 排列:
std::next_permutation(first, last, [comp]): 生成下一个字典序更大的排列。若已是最大排列,则变为最小排列并返回false。std::prev_permutation(first, last, [comp]): 生成上一个字典序更小的排列。
- 数值算法 (头文件
<numeric>):std::accumulate(first, last, init, [op]): 计算范围元素的和(或其他二元操作op的结果),初始值为init。std::iota(first, last, val): 用val,val+1,val+2, ... 填充范围。 (C++11)
- 最值:
std::min(a, b, [comp]),std::max(a, b, [comp]): 返回两者中较小/较大的。std::min_element(first, last, [comp]): 返回指向范围内最小元素的迭代器。O(N)。std::max_element(first, last, [comp]): 返回指向范围内最大元素的迭代器。O(N)。
- 示例:
- 提供大量通用算法,作用于迭代器指定的范围
#include <algorithm> // Most algorithms
#include <vector>
#include <iostream>
#include <numeric> // For std::iota, std::accumulate
#include <string>
bool is_odd(int i) { return i % 2 != 0; }
struct MyStruct {
int id;
std::string name;
bool operator<(const MyStruct& other) const { // For sort
return id < other.id;
}
};
int main() {
std::vector<int> v = {3, 1, 4, 1, 5, 9, 2, 6};
std::sort(v.begin(), v.end()); // v: {1, 1, 2, 3, 4, 5, 6, 9}
for (int x : v) std::cout << x << " "; std::cout << std::endl;
bool found_5 = std::binary_search(v.begin(), v.end(), 5);
std::cout << "Found 5? " << (found_5 ? "Yes" : "No") << std::endl;
auto it_lower = std::lower_bound(v.begin(), v.end(), 3);
std::cout << "Lower bound for 3: " << *it_lower << " at index " << (it_lower - v.begin()) << std::endl;
std::reverse(v.begin(), v.begin() + 4); // Reverse first 4 elements
for (int x : v) std::cout << x << " "; std::cout << std::endl; // v: {3, 2, 1, 1, 4, 5, 6, 9}
// Using unique on a sorted range (after sorting again)
std::vector<int> v_dup = {1, 2, 2, 3, 3, 3, 4, 2};
std::sort(v_dup.begin(), v_dup.end()); // {1, 2, 2, 2, 3, 3, 3, 4}
auto last_unique = std::unique(v_dup.begin(), v_dup.end());
// v_dup is now {1, 2, 3, 4, ?, ?, ?, ?} where ? are unspecified values
// last_unique points to the first ?
v_dup.erase(last_unique, v_dup.end()); // Actually remove the duplicates
for (int x : v_dup) std::cout << x << " "; std::cout << std::endl; // {1, 2, 3, 4}
std::vector<int> nums(5);
std::iota(nums.begin(), nums.end(), 10); // nums: {10, 11, 12, 13, 14}
for (int x : nums) std::cout << x << " "; std::cout << std::endl;
int sum = std::accumulate(nums.begin(), nums.end(), 0);
std::cout << "Sum: " << sum << std::endl; // 60
std::cout << "Min element: " << *std::min_element(v.begin(), v.end()) << std::endl;
std::cout << "Max element: " << *std::max_element(v.begin(), v.end()) << std::endl;
std::string s = "abc";
do {
std::cout << s << " ";
} while (std::next_permutation(s.begin(), s.end())); // abc acb bac bca cab cba
std::cout << std::endl;
// Find_if example
std::vector<int> numbers = {1, 2, 3, 4, 5, 6};
auto first_odd = std::find_if(numbers.begin(), numbers.end(), is_odd);
if (first_odd != numbers.end()) {
std::cout << "First odd number: " << *first_odd << std::endl; // 1
}
// Sort custom objects
std::vector<MyStruct> records = {{2, "B"}, {1, "A"}, {3, "C"}};
std::sort(records.begin(), records.end()); // Uses MyStruct::operator<
for(const auto& r : records) std::cout << r.id << ":" << r.name << " ";
std::cout << std::endl; // 1:A 2:B 3:C
return 0;
}
练习题 (STL部分):
vector** 和 **pair: 读取N个学生的姓名和分数,存入std::vector<std::pair<int, std::string>>(分数在前,方便排序)。按分数降序排序,若分数相同则按姓名字典序升序。输出排序后的结果。set: 读取N个整数,输出其中所有不同的整数,并按升序排列。map: 读取一个字符串,统计其中每个字符出现的次数并输出。priority_queue: 实现一个简单的任务调度器。有N个任务,每个任务有截止时间d和利润p。每个任务花费1个单位时间。求最大总利润。 (提示:按截止时间排序任务,用最小堆维护已选任务的利润)。bitset: 给定两个整数A和B (0 <= A, B < 2^30),用std::bitset<32>表示它们,并计算它们的按位与、或、异或结果,并以二进制串形式输出。algorithm:- 给定一个整数数组,找到第k小的元素 (不使用
std::nth_element,可以先排序)。 - 给定一个已排序的整数数组和一个目标值
target,找到target在数组中出现的第一个位置和最后一个位置(如果存在)。使用std::lower_bound和std::upper_bound。
- 给定一个整数数组,找到第k小的元素 (不使用
总结与展望
今天我们复习了Linux基础、C++编译调试、类和STL的核心内容。这些工具和概念是信息学竞赛的日常,熟练掌握它们能让你更专注于算法和问题本身。
- 多实践: 命令行、编辑器、GDB都需要多用才能熟练。
- 多刷题: 通过题目巩固STL各种容器和算法的用法,体会它们的效率和适用场景。
- 深入理解: 不仅仅是会用,更要理解其内部原理(如红黑树、哈希表、堆),这有助于你选择最优的数据结构。
希望大家回去后好好消化,完成练习题。下次课再见!

浙公网安备 33010602011771号