附录 C:C++ 竞赛编程技巧

本附录收集了竞赛程序员每天使用的最有用的 C++ 技巧、宏、模板和代码片段,可以在竞赛中节省大量时间并让代码运行更快。


C.1 快速 I/O

对 I/O 密集型题目最重要的性能优化:

// 始终在 main() 开头加上这两行
ios_base::sync_with_stdio(false);  // 断开 C 和 C++ I/O 流的同步
cin.tie(NULL);                      // 解除 cin 和 cout 的绑定

// 为什么有效:
// sync_with_stdio(false):默认情况下 C++ 与 C I/O(printf/scanf)
//   同步以保持兼容性,关闭后 cin/cout 快得多。
// cin.tie(NULL):默认情况下 cin 在每次读取前清空 cout,
//   解除绑定消除这个不必要的清空。

性能差异很显著——这两行应该出现在每个解法中:

Fast I/O Speed Comparison

文件 I/O(USACO 传统题):

freopen("problem.in",  "r", stdin);   // 将 cin 重定向到文件(将 "problem" 替换为实际名称)
freopen("problem.out", "w", stdout);  // 将 cout 重定向到文件
// 这两行之后 cin/cout 正常使用但读写文件

C.2 常用宏和 typedef

📄 查看代码:C.2 常用宏和 typedef
// 较短的类型名
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef vector<int> vi;
typedef vector<ll> vll;

// 缩写操作
#define pb push_back
#define pf push_front
#define all(v) (v).begin(), (v).end()
#define rall(v) (v).rbegin(), (v).rend()
#define sz(v) ((int)(v).size())
#define fi first
#define se second

// 循环宏(谨慎使用——可能影响可读性)
#define FOR(i, a, b) for(int i = (a); i < (b); i++)
#define REP(i, n) FOR(i, 0, n)

// 最小/最大值缩写
#define chmin(a, b) a = min(a, b)
#define chmax(a, b) a = max(a, b)

// 使用示例:
// vi v; v.pb(5);        → v.push_back(5)
// sort(all(v));         → sort(v.begin(), v.end())
// cout << sz(v) << "\n";→ cout << (int)v.size() << "\n"
// FOR(i, 1, n+1) { ... }→ for(int i = 1; i < n+1; i++) { ... }

C.3 GCC 编译指令提速

📄 查看代码:C.3 GCC 编译指令提速
// 这些 pragma 在 GCC 编译器(USACO 评测机使用)上可提速 2-4 倍
#pragma GCC optimize("O3,unroll-loops")
#pragma GCC target("avx2,bmi,bmi2,popcnt")

// 放在 #include 之前
// 警告:O3 和 avx2 可能导致微妙的数值差异
//   (整数题通常没问题,浮点数要小心)

// 更安全的版本(只有 O2,无向量指令):
#pragma GCC optimize("O2")

// 带 pragma 的完整竞赛模板:
#pragma GCC optimize("O3,unroll-loops")
#pragma GCC target("avx2")
#include <bits/stdc++.h>
using namespace std;
// ... 你的代码

C.4 实用数学:GCD、LCM、模运算

📄 查看代码:C.4 实用数学:GCD、LCM、模运算
#include <bits/stdc++.h>
using namespace std;

// ─── GCD 和 LCM ──────────────────────────────────────────────────────────────

// C++17:来自 <numeric> 的 std::gcd 和 std::lcm
#include <numeric>
int g = gcd(12, 8);            // 4
int l = lcm(4, 6);             // 12

// C++14 及以前:来自 <algorithm> 的 __gcd
int g2 = __gcd(12, 8);         // 4

// 自定义 GCD(辗转相除法):
ll mygcd(ll a, ll b) { return b ? mygcd(b, a%b) : a; }
ll mylcm(ll a, ll b) { return a / mygcd(a,b) * b; }  // 先除防溢出!

// ─── 模运算 ──────────────────────────────────────────────────────────────────

const ll MOD = 1e9 + 7;  // 标准 USACO/Codeforces 模数

// 加法:(a + b) % MOD
ll addmod(ll a, ll b) { return (a + b) % MOD; }

// 减法:(a - b + MOD) % MOD  ← 取模前总要加 MOD 防负数
ll submod(ll a, ll b) { return (a - b + MOD) % MOD; }

// 乘法:(a * b) % MOD
ll mulmod(ll a, ll b) { return (a % MOD) * (b % MOD) % MOD; }

// 快速幂:a^b mod MOD,O(log b)
ll power(ll base, ll exp, ll mod = MOD) {
    ll result = 1;
    base %= mod;
    while (exp > 0) {
        if (exp & 1) result = result * base % mod;  // 奇数指数
        base = base * base % mod;                    // 平方
        exp >>= 1;                                   // 减半指数
    }
    return result;
}

// 模逆元(a^{-1} mod p,p 为质数):
ll modinv(ll a, ll mod = MOD) { return power(a, mod-2, mod); }
// 这使用费马小定理:a^{p-1} ≡ 1 (mod p),p 为质数
// 所以 a^{-1} ≡ a^{p-2} (mod p)

// 模除法:(a / b) mod p = (a * b^{-1}) mod p
ll divmod(ll a, ll b) { return mulmod(a, modinv(b)); }

// 预计算阶乘后的组合数 C(n, k) mod p
const int MAXN = 200001;
ll fact[MAXN], inv_fact[MAXN];

void precompute_factorials() {
    fact[0] = 1;
    for (int i = 1; i < MAXN; i++) fact[i] = fact[i-1] * i % MOD;
    inv_fact[MAXN-1] = modinv(fact[MAXN-1]);
    for (int i = MAXN-2; i >= 0; i--) inv_fact[i] = inv_fact[i+1] * (i+1) % MOD;
}

ll C(int n, int k) {
    if (k < 0 || k > n) return 0;
    return fact[n] * inv_fact[k] % MOD * inv_fact[n-k] % MOD;
}

C.5 实用代码片段

并查集(DSU)模板

📄 查看代码:并查集(DSU)模板
// DSU——带大小追踪的完整模板
struct DSU {
    vector<int> parent, sz;

    DSU(int n) : parent(n+1), sz(n+1, 1) {
        iota(parent.begin(), parent.end(), 0);
    }

    int find(int x) {
        if (parent[x] != x) parent[x] = find(parent[x]);  // 路径压缩
        return parent[x];
    }

    bool unite(int x, int y) {
        x = find(x); y = find(y);
        if (x == y) return false;
        if (sz[x] < sz[y]) swap(x, y);   // 按大小合并
        parent[y] = x;
        sz[x] += sz[y];
        return true;
    }

    bool connected(int x, int y) { return find(x) == find(y); }
    int size(int x) { return sz[find(x)]; }   // x 所在分量的大小
};

// 使用:
DSU dsu(n);
dsu.unite(1, 2);
cout << dsu.connected(1, 3) << "\n";   // 0(假)
cout << dsu.size(1) << "\n";           // 2

线段树(单点更新,区间查询)

📄 查看代码:线段树(单点更新,区间查询)
// 线段树——支持:
//   point_update(i, val):设置位置 i 为 val
//   query(l, r):[l, r] 的和
// 所有操作 O(log N)

struct SegTree {
    int n;
    vector<ll> tree;

    SegTree(int n) : n(n), tree(4*n, 0) {}

    void update(int node, int start, int end, int idx, ll val) {
        if (start == end) {
            tree[node] = val;
            return;
        }
        int mid = (start + end) / 2;
        if (idx <= mid) update(2*node, start, mid, idx, val);
        else            update(2*node+1, mid+1, end, idx, val);
        tree[node] = tree[2*node] + tree[2*node+1];
    }

    ll query(int node, int start, int end, int l, int r) {
        if (r < start || end < l) return 0;
        if (l <= start && end <= r) return tree[node];
        int mid = (start + end) / 2;
        return query(2*node, start, mid, l, r)
             + query(2*node+1, mid+1, end, l, r);
    }

    void update(int i, ll val) { update(1, 1, n, i, val); }
    ll query(int l, int r) { return query(1, 1, n, l, r); }
};

BFS 模板

📄 查看代码:BFS 模板
// 网格 BFS——无权网格中的最短路径
int bfs_grid(vector<string>& grid, int sr, int sc, int er, int ec) {
    int R = grid.size(), C = grid[0].size();
    vector<vector<int>> dist(R, vector<int>(C, -1));
    queue<pair<int,int>> q;
    int dr[] = {-1, 1, 0, 0};
    int dc[] = {0, 0, -1, 1};

    dist[sr][sc] = 0;
    q.push({sr, sc});

    while (!q.empty()) {
        auto [r, c] = q.front(); q.pop();
        for (int d = 0; d < 4; d++) {
            int nr = r + dr[d], nc = c + dc[d];
            if (nr >= 0 && nr < R && nc >= 0 && nc < C
                && grid[nr][nc] != '#' && dist[nr][nc] == -1) {
                dist[nr][nc] = dist[r][c] + 1;
                q.push({nr, nc});
            }
        }
    }
    return dist[er][ec];
}

二分答案模板

📄 查看代码:二分答案模板
// 二分答案——最大化满足 check(X) 为真的 X
// 前提:check 是单调的(false...false...true...true)
template<typename T, typename F>
T binary_search_ans(T lo, T hi, F check) {
    T ans = lo;
    while (lo <= hi) {
        T mid = lo + (hi - lo) / 2;
        if (check(mid)) { ans = mid; lo = mid + 1; }
        else { hi = mid - 1; }
    }
    return ans;
}

// 使用示例:找最大 D 使 canPlace(D) 为真
int result = binary_search_ans(1, maxDist, canPlace);

C.6 值得了解的内置函数

📄 查看代码:C.6 值得了解的内置函数
// ─── 整数操作 ─────────────────────────────────────────────────────────────────

__builtin_popcount(x)      // 统计 x 中置位数(int)
__builtin_popcountll(x)    // 统计 x 中置位数(long long)
__builtin_clz(x)           // 统计前导零数(int,x > 0)
__builtin_ctz(x)           // 统计尾零数(int,x > 0)

// 示例:
__builtin_popcount(0b1011) == 3    // 三个 1 位
__builtin_ctz(0b1000)      == 3    // 三个尾零
(31 - __builtin_clz(x))            // floor(log2(x))

// ─── 位运算技巧 ──────────────────────────────────────────────────────────────

// 检查 x 是否是 2 的幂:
bool isPow2 = (x > 0) && !(x & (x-1));

// 提取最低置位:
int lsb = x & (-x);

// 清除最低置位:
x = x & (x-1);

// 枚举位掩码的所有子集(用于状压 DP):
for (int sub = mask; sub > 0; sub = (sub-1) & mask) {
    // 处理 mask 的子集 sub
}

// ─── 实用 STL 函数 ────────────────────────────────────────────────────────────

// next_permutation:遍历所有排列
sort(v.begin(), v.end());    // 从有序开始
do {
    // v 是当前排列
} while (next_permutation(v.begin(), v.end()));

C.7 完整竞赛模板

📄 查看代码:C.7 完整竞赛模板
// ────────────────────────────────────────────────────────────────────────────
// 竞赛编程模板——C++17
// ────────────────────────────────────────────────────────────────────────────
#pragma GCC optimize("O2")
#include <bits/stdc++.h>
using namespace std;

// 类型别名
typedef long long ll;
typedef pair<int,int> pii;
typedef vector<int> vi;

// 便捷宏
#define pb push_back
#define all(v) (v).begin(), (v).end()
#define sz(v) ((int)(v).size())
#define fi first
#define se second

// 常量
const ll MOD = 1e9 + 7;
const ll INF = 1e18;
const int MAXN = 200005;

// 快速幂取模
ll power(ll base, ll exp, ll mod = MOD) {
    ll res = 1; base %= mod;
    for (; exp > 0; exp >>= 1) {
        if (exp & 1) res = res * base % mod;
        base = base * base % mod;
    }
    return res;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    // 文件 I/O 时取消注释:
    // freopen("problem.in", "r", stdin);
    // freopen("problem.out", "w", stdout);

    // ── 你的解法 ──

    return 0;
}

C.8 常用模式和惯用法

📄 查看代码:C.8 常用模式和惯用法
// ─── 读取 N 个整数到向量 ──────────────────────────────────────────────────────
int n; cin >> n;
vi a(n);
for (int &x : a) cin >> x;

// ─── 二维向量初始化 ──────────────────────────────────────────────────────────
int R, C;
vector<vector<int>> grid(R, vector<int>(C, 0));

// ─── 自定义条件排序 ──────────────────────────────────────────────────────────
sort(all(v), [](const auto &a, const auto &b) {
    return a.weight < b.weight;  // 按重量升序排序
});

// ─── 带下标的最小/最大值 ─────────────────────────────────────────────────────
auto maxIt = max_element(all(v));
int maxVal = *maxIt;
int maxIdx = maxIt - v.begin();

// ─── 从有序向量删除重复 ──────────────────────────────────────────────────────
sort(all(v));
v.erase(unique(all(v)), v.end());

// ─── 整数平方根(精确,无浮点问题)──────────────────────────────────────────
ll isqrt(ll n) {
    ll r = sqrtl(n);
    while (r*r > n) r--;
    while ((r+1)*(r+1) <= n) r++;
    return r;
}

// ─── 检查数是否为质数 ─────────────────────────────────────────────────────────
bool isPrime(ll n) {
    if (n < 2) return false;
    if (n == 2) return true;
    if (n % 2 == 0) return false;
    for (ll i = 3; i * i <= n; i += 2) {
        if (n % i == 0) return false;
    }
    return true;
}

// ─── 埃拉托斯特尼筛法(N 以内所有质数)───────────────────────────────────────
vector<bool> sieve(int N) {
    vector<bool> is_prime(N+1, true);
    is_prime[0] = is_prime[1] = false;
    for (int i = 2; i * i <= N; i++) {
        if (is_prime[i]) {
            for (int j = i*i; j <= N; j += i)
                is_prime[j] = false;
        }
    }
    return is_prime;
}

C.9 调试技巧

📄 查看代码:C.9 调试技巧
// 用 cerr 调试输出(评测机通常忽略标准错误)
#ifdef DEBUG
    #define dbg(x) cerr << #x << " = " << x << "\n"
    #define dbgv(v) cerr << #v << ": "; for(auto x:v) cerr << x << " "; cerr << "\n"
#else
    #define dbg(x)
    #define dbgv(v)
#endif
// 调试模式编译:g++ -DDEBUG -o sol sol.cpp  (启用调试输出)
// 正常编译:    g++ -o sol sol.cpp  (移除调试输出)

// 使用:
int x = 42;
dbg(x);         // 打印:x = 42(仅调试模式)
vi v = {1,2,3};
dbgv(v);        // 打印:v: 1 2 3(仅调试模式)

// 用消毒器检测内存错误和未定义行为:
// g++ -fsanitize=address,undefined -O1 -o sol sol.cpp
// 非常适合发现:
//   - 数组越界访问
//   - 整数溢出(带 -fsanitize=signed-integer-overflow)
//   - 使用未初始化内存
//   - 空指针解引用

树状数组(BIT)——带更新的前缀和

Binary Indexed Tree

树状数组使用最低置位技巧实现 O(log N) 的前缀和查询和更新。下标 i 负责范围 [i - lowbit(i) + 1, i],其中 lowbit(i) = i & (-i)

📄 C++ 完整代码
// 树状数组 / BIT——O(log N) 更新和前缀查询
struct BIT {
    int n;
    vector<long long> tree;
    BIT(int n) : n(n), tree(n + 1, 0) {}

    // 在位置 i 添加 val(1-indexed)
    void update(int i, long long val) {
        for (; i <= n; i += i & (-i))
            tree[i] += val;
    }

    // 前缀和 [1..i]
    long long query(int i) {
        long long sum = 0;
        for (; i > 0; i -= i & (-i))
            sum += tree[i];
        return sum;
    }

    // 区间和 [l..r]
    long long query(int l, int r) { return query(r) - query(l - 1); }
};