C++ / ACM 自学笔记 · v5.0

快速 I/O 与竞赛模板

这部分作为提交时的查阅清单。初学者可先运行两整数求和的输入输出例子,再顺序学习正文;学过函数、类型和头文件后,回来理解通用模板中的每一行,不需要先掌握整份模板才能开始语言基础。

标准流加速

大多数入门题使用下面两行设置已经足够。把它们放在 main 开头、任何输入输出之前。

快在哪里? C 与 C++ 标准流保持同步、读入前自动刷新关联输出,都可能产生额外工作。普通批量题只需把输入一次次读完、最后按规则输出,所以可以关闭这两项配合;它们不改变题目的计算逻辑。

模板在 main 开头先设置标准流,再用 cin、cout 完成输入输出。关闭同步后,C 与 C++ 的标准流各自使用缓冲;例如 scanf 已把一段输入读进自己的缓冲,cin 再读取时就可能接不到这段数据。因此,下面的标准流模板从读入到输出都使用 cin、cout,后面的 fread 模板则使用 C 输入输出。

'\n' 只写换行字符,endl 还会强制刷新。大量输出时,逐行刷新会增加开销;交互题中,程序输出询问后刷新,另一端收到询问才能继续回答,因此询问后可以使用 endl 或 cout.flush()。

#include <iostream>
using namespace std;

ios::sync_with_stdio(false); // 关闭 C 与 C++ 标准流同步。
cin.tie(nullptr);            // 读入前不再自动刷新 cout。
  • sync_with_stdio(false) 关闭 C++ 标准流与 C 标准流的同步。
  • cin.tie(nullptr) 取消 cin 默认关联的输出刷新。
  • cout 默认没有绑定输入流,通常不需要额外写 cout.tie(nullptr)。
  • endl 会换行并刷新,普通批量输出使用 '\n' 即可。

注意: 交互题需要在输出询问后主动刷新,例如 cout << query << '\n' << flush;。取消自动刷新以后,忘记刷新可能让双方一直等待。

基础竞赛模板:使用 ll 类型别名

下面适合 GNU C++17 等支持 bits/stdc++.h 的评测环境。每题先确认是否有多组输入,再决定是否读取 T。

#include <bits/stdc++.h> // GNU 扩展,非标准头文件
using namespace std;
using ll = long long;

void solve() {
    // 每组数据的变量尽量放在这里,自动重新初始化
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int T = 1;
    // cin >> T;  // 题目明确给出测试组数时开启
    while (T--) {
        solve();
    }
    return 0;
}

如果评测要求可移植的标准 C++,把第一行换为当前程序实际需要的标准头文件;只做整数输入输出时使用 <iostream> 即可。

竞赛宏模板:统一使用 long long

下面是一份常用的竞赛宏模板。可以与显式 ll 模板择一使用:

#include <bits/stdc++.h> // 需要支持这个扩展头文件的 GNU 环境。
#define int long long    // 将后续 int 统一替换成 long long。
#define endl '\n'        // 仅换行,不刷新输出。
using namespace std;

void solve() {
    // 在这里实现一组数据的解法。
}

signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int T = 1;
    // cin >> T;
    while (T--)
        solve();
}

说明:

  • #define int long long 是预处理阶段的文本替换,不是类型别名。后续声明中的 int 会统一使用较宽整数,估算数组内存时按实际元素大小计算;学习 STL 后也按容器实际元素类型估算。
  • signed main() 中的 signed 表示 signed int,没有显式的 int 记号,因此不会被宏替换;main 仍返回标准要求的 int。若写 int main(),返回类型也会被替换,因此模板使用 signed main()。
  • 宏不会把字面量 1 自动变成 1LL。例如 1 << 40 的左操作数仍是普通 int,应根据需要写为 1LL << 40 或 1ULL << 40。
  • #define endl '\n' 取消了刷新作用;后续写 std::endl 还会被替换成非法代码。需要刷新时使用 flush。
  • 这个类型宏还会改变重载、接口匹配和类型组合。例如 scanf("%d", &x) 不再匹配替换后的 long long x。通用讲解示例使用 ll 或显式 long long,更容易看清类型。
  • cin.tie(0), cout.tie(0); 使用逗号表达式执行两次调用,0 在这里表示空指针;现代写法可使用 nullptr。

这份模板适用于常见 GNU C++ 竞赛环境。统一整数类型可以减少累计和与乘法的类型遗漏;调试接口、移位和内存问题时,仍要按宏替换后的实际类型检查。

多组输入、EOF 与输出精度

有明确测试组数:

#include <iostream>
using namespace std;

void solve() {
    // 示例每组读入两个整数,真正做题时替换为当前题目的解法。
    long long a, b;
    cin >> a >> b;
    cout << a + b << '\n'; // 输出这一组两数之和。
}

int main() {
    int T;
    cin >> T;
    while (T--) {
        solve(); // 每次调用处理恰好一组数据。
    }
}

没有组数,读到文件结束:

下面每组读入两个整数,用 long long 保存并输出它们的和。

#include <iostream>
using namespace std;

int main() {
    long long a, b;
    while (cin >> a >> b) {
        cout << a + b << '\n'; // 每次成功读到一对数,处理恰好一组。
    }
}

while(cin>>a>>b) 把“本次完整读入成功”作为继续条件。EOF 表示输入结束,格式错误也会令读取失败。不要先判断 !cin.eof() 再读取;末尾标志通常在一次读取尝试后才设置,可能多处理一组旧数据。读到一组数据的中途就结束时,循环条件为假,这一组不会进入计算。

输出固定小数位:

#include <iostream>
#include <iomanip>
using namespace std;

double answer = 1.0 / 3.0;
// fixed 下的精度表示小数点后 6 位,输出 0.333333。
cout << fixed << setprecision(6) << answer << '\n';

注意: 每组数据开始时重新初始化它需要的数组、计数器和答案,避免上一组结果影响下一组。后面学习容器后,也要按每组数据的实际长度重新准备存储。

查阅笔记正文与全部题解 · Ctrl / ⌘ K