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: 显示行号。
    • 示例:
      1. vim hello.cpp
      2. i 进入插入模式。
      3. 输入你的C++代码。
      4. Esc 返回命令模式。
      5. 输入 :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_name
    • source.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调试流程示例:
    1. g++ -g my_program.cpp -o my_program
    2. gdb ./my_program
    3. (gdb) b main (在main函数设断点)
    4. (gdb) r (运行程序,会在main处停下)
    5. (gdb) b 15 (假设第15行有问题,设断点)
    6. (gdb) c (继续运行到第15行)
    7. (gdb) p var_name (查看变量var_name的值)
    8. (gdb) n (单步执行)
    9. ... (重复查看变量、单步执行,直到找到问题)
    10. (gdb) q (退出)

练习题 (环境部分):

  1. 创建一个名为 noip_review 的目录。
  2. 进入 noip_review 目录,创建一个名为 test.cpp 的C++文件,内容为打印 "Hello NOIP!"。
  3. 使用 g++ 编译 test.cpp,使用C++14标准,开启O2优化和所有警告,输出可执行文件名为 hello
  4. 运行 hello 程序。
  5. 使用 time 命令测量 hello 程序的运行时间。
  6. (选做) 在 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` 等。

练习题 (类部分):

  1. 设计一个 Rectangle 类,包含私有成员 widthheight (浮点数)。
  2. Rectangle 类编写构造函数,可以初始化宽度和高度。
  3. 编写成员函数 getArea() 计算并返回矩形面积。
  4. 编写成员函数 getPerimeter() 计算并返回矩形周长。
  5. (选做) 重载 Rectangle 类的 + 运算符,使其返回一个新的 Rectangle 对象,其宽度为两者宽度之和,高度为两者高度之和。
  6. (选做) 重载 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和堆结构。
    • 迭代器 (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 < btrue,则 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 不存在,则插入;若存在,则修改。
          • 注意: 仅查询时使用 [] 可能意外插入元素。建议用 findat (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) 返回一个 pair of 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]): 返回一个 pair of 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]): 移除有序范围中的连续重复元素(实际是把不重复的移到前面,返回不重复部分的尾后迭代器)。通常先 sortunique
      • 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_valnew_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部分):

  1. vector** 和 **pair: 读取N个学生的姓名和分数,存入 std::vector<std::pair<int, std::string>> (分数在前,方便排序)。按分数降序排序,若分数相同则按姓名字典序升序。输出排序后的结果。
  2. set: 读取N个整数,输出其中所有不同的整数,并按升序排列。
  3. map: 读取一个字符串,统计其中每个字符出现的次数并输出。
  4. priority_queue: 实现一个简单的任务调度器。有N个任务,每个任务有截止时间 d 和利润 p。每个任务花费1个单位时间。求最大总利润。 (提示:按截止时间排序任务,用最小堆维护已选任务的利润)。
  5. bitset: 给定两个整数A和B (0 <= A, B < 2^30),用 std::bitset<32> 表示它们,并计算它们的按位与、或、异或结果,并以二进制串形式输出。
  6. algorithm:
    • 给定一个整数数组,找到第k小的元素 (不使用 std::nth_element,可以先排序)。
    • 给定一个已排序的整数数组和一个目标值 target,找到 target 在数组中出现的第一个位置和最后一个位置(如果存在)。使用 std::lower_boundstd::upper_bound

总结与展望

今天我们复习了Linux基础、C++编译调试、类和STL的核心内容。这些工具和概念是信息学竞赛的日常,熟练掌握它们能让你更专注于算法和问题本身。

  • 多实践: 命令行、编辑器、GDB都需要多用才能熟练。
  • 多刷题: 通过题目巩固STL各种容器和算法的用法,体会它们的效率和适用场景。
  • 深入理解: 不仅仅是会用,更要理解其内部原理(如红黑树、哈希表、堆),这有助于你选择最优的数据结构。

希望大家回去后好好消化,完成练习题。下次课再见!


posted @ 2025-06-01 09:26  左边之上  阅读(17)  评论(0)    收藏  举报