乐于分享
好东西不私藏

蓝桥杯/ACM算法竞赛核心模板速查:枚举、前缀、差分、双指针与二分 篇

蓝桥杯/ACM算法竞赛核心模板速查:枚举、前缀、差分、双指针与二分 篇
最近正处于各项算法竞赛的冲刺期,比如蓝桥杯、码蹄杯、CCPC省赛都即将到来,时间紧任务重。本文整理了竞赛中频繁出现、也是最容易拿分的一章:从基础的子集枚举到让人头秃的二分边界。全是纯金模板,建议收藏,随时翻阅!
附上由我个人精心整理的一份面向程序设计竞赛的C++17 算法与模板速查资料,适合作为赛前复习、现场赛打印资料以及日常查阅手册使用,传送门:
https://github.com/dcjaychou-design/acm-icpc-cpp17-reference

1 子集枚举

1.1 枚举0 ∼ 2n - 1

int n;for (int mask = 0; mask < (1 << n); ++mask) {    for (int i = 0; i < n; ++i)     {        if (mask >> i & 1        {            // choose i        }    }

1.2 枚举一个集合的所有子集

for (int sub = mask; ; sub = (sub - 1) & mask){    //subisasubsetofmask    if (sub == 0) break;}

1.3 Meet-in-the-Middle 思路

当 n 约为 40,直接 2n 不现实,可拆成两半分别枚举,再排序或双指针合并。

2 一维前缀和

vector<long long> pre(n + 10);for (int i = 1; i <= n; ++i)    pre[i] = pre[i - 1] + a[i];longlongsunRange(int l, int r)    return pre[r] - pre[l - 1];

3 二维前缀和

vector<vector> s(n + 1, vector(m + 10)); for (int i = 1; i <= n; ++i)    for (int j = 1; j <= m; ++j)         s[i][j] = a[i][j] + s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1]long long sumRect(int x1, int y1, int x2, int y2)     return s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1]

4 差分

4.1 一维差分

vector diff(n + 20);auto addRange = [&](int l, int r, long long v)     diff[l] += v;    diff[r + 1] -= v; };for (int i = 1; i <= n; ++i)     diff[i] += diff[i - 1];    a[i] += diff[i]; 

4.2 二维差分

vector<vector> d(n + 2, vector(m + 20));auto addRect = [&](int x1, int y1, int x2, int y2, long long v)     d[x1][y1] += v;     d[x2 + 1][y1] -= v;     d[x1][y2 + 1] -= v;     d[x2 + 1][y2 + 1] += v; };for (int i = 1; i <= n; ++i)     for (int j = 1; j <= m; ++j)     {        d[i][j] += d[i - 1][j] + d[i][j - 1] - d[i - 1][j - 1];        a[i][j] += d[i][j];     }

5 离散化

vector xs = raw; sort(xs.begin(), xs.end()); xs.erase(unique(xs.begin(), xs.end()), xs.end());auto getId = [&](long long x) {    return int(lower_bound(xs.begin(), xs.end(), x) - xs.begin()) + 1}; 

6 双指针与滑动窗口

6.1 最长满足约束的区间

适合“连续子数组”且窗口条件随左右端点单调变化。

int l = 0, ans = 0;long long sum = 0;for (int r = 0; r < n; ++r) { sum += a[r]; while (l <= r && sum > limit)  sum -= a[l++]; ans = max(ans, r - l + 1);}

6.2 有序数组去重式双指针

sort(a.begin(), a.end());int j = 0;for (int i = 0; i < n; ++i) if (i == 0 || a[i] != a[i - 1]) a[j++] = a[i];a.resize(j);

7 区间合并

vector<pair<int,int>> segs;sort(segs.begin(), segs.end());vector<pair<int,int>> merged;for (auto [l, r] : segs) { if (merged.empty() || merged.back().second < l) merged.push_back({l, r}); else merged.back().second = max(merged.back().second, r);

8 整数二分

8.1 找第一个满足条件的位置

条件形如 false, false, false, true, true。

int l = 0, r = n; // answer in [0, n]while (l < r) { int mid = l + (r - l) / 2; if (check(mid)) r = mid; else l = mid + 1;}// l is first satisfying position 

8.2 找最后一个满足条件的位置

条件形如 true, true, true, false, false。

int l = 0, r = n; // answer in [0, n]while (l < r) { int mid = l + (r - l + 1) / 2; if (check(mid)) l = mid; else r = mid - 1;}// l is last satisfying position 

9 二分答案

先确定答案区间。

写出只回答“能不能”的 check(mid)。

验证单调性,再决定找第一个还是最后一个。

long long l = 0, r = 1e18;while (l < r) { long long mid = l + (r - l) / 2; if (check(mid)) r = mid; else l = mid + 1;}cout << l << '\n'

10 浮点二分

double l = 0, r = 1e9;for (int it = 0; it < 100; ++it) { double mid = (l + r) / 2; if (check(mid)) r = mid; else l = mid;}cout << fixed << setprecision(10) << r << '\n'
二分的边界(l < r 还是 l <= r)常常是赛场上的“红线”,多写一个 1 或少写一个 1 就是 AC 与 WA 的距离。如果觉得这份速查表对你有用,不妨点个“在看”或者分享给一同打比赛的队友。
💬 互动话题:你在写二分或者差分的时候,最常犯的错误是什么?欢迎在评论区留言交流!