本讲定位:这是 CSP-J 复赛出现频率最高的一讲。
cspj-series/cspj-round2-analysis-2019-2025.md的逐题统计显示,2019–2025 七年 28 道 J 组复赛题里,主考点落在”枚举 / 模拟 / 构造”的有 6 道,单独第一(basic-03排序与贪心、ds-series、dp-series三线各 5 道并列第二)。T1、T2 两个位置几乎被它承包。但”枚举”和”模拟”最容易被当成”不需要学的东西”——不就是照着题目写吗? 本讲要说明的恰恰是:这两件事有明确的、可以练的工程方法,而且做错的代价被严重低估。本讲实测的五种模拟错法里,最高的一种在大数据上 99.40% 的测试组会出错。
先把本讲最硬的几个数字放在前面。
⚠️ 关于编译选项,必须先说清楚。 本讲的代码块由
tools/check-cpp-blocks.py用g++ -std=c++11(不开-O2) 编译运行,下面各节”实测输出”里的耗时就是该环境的数字。而正式评测机通常开-O2,同样的代码会快 3~8 倍。本讲凡是涉及”多大规模能过”的结论,两套数字都给,并标明是哪一套。这不是形式主义:本讲实测中有一条结论在两种编译选项下方向相反(见第 2 条),如果只测一种就会写出错误的结论。
枚举对象选错,规模直接爆炸:分糖果模型(P7909)里,朴素枚举每一个 $k$,$R-L+1=10^9$ 时要 3.0203 秒(不开 O2;开 O2 是 0.8845 秒),$R-L+1=5\times10^9$ 时要 15.1132 / 4.4298 秒;换成”只枚举 $\min(R-L+1,\,n)$ 个”,两种编译选项下都恒为 0.000000 秒——因为区间够长时答案直接是 $n-1$,一次循环都不用跑。
⚠️ 剪掉对称性:实际加速比不等于枚举量之比,而且差的方向取决于开不开 O2。
| 枚举量之比 | 实测加速比(不开 -O2) |
实测加速比(开 -O2) |
|
|---|---|---|---|
| 二重循环加 $i<j$ | 2.00x | 2.12x(偏高) | 1.52x(偏低) |
| 三重循环加 $i<j<k$ | 6.02x | 9.82x(偏高) | 5.20x(偏低) |
所以”少枚举到 1/6 就快 6 倍”两种情况下都是错的,而且错的方向还相反。开 -O2 时不足额,是因为 for (j=i+1; ...) 内层长度不定、分支预测与循环展开都吃亏;不开 -O2 时超额,是因为循环本身的固定开销占了大头,少跑的那部分连带省掉了这份开销。记住结论就一句:加速比要实测,不能从枚举量推。
子集枚举 s=(s-1)&t 把 $4^n$ 变成 $3^n$,可行规模整整多 4 位。 一秒墙:不开 O2 时 $4^n$ 在 $n\le14$、$3^n$ 在 $n\le18$(0.8724 s);开 O2 时是 $n\le15$ 与 $n\le19$(0.8431 s,$n=20$ 要 2.5145 s)。$n=15$ 时两种写法的比值:不开 O2 是 1.8691 s vs 0.0327 s(57 倍),开 O2 是 0.8595 s vs 0.0107 s(80 倍)。
⚠️ 子集枚举写成 s=(s-1)&s,指数级会悄悄退化成线性。 $n=14$ 时本该平均枚举 291.9 个子集,实际只枚举 8.0 个(是 $n/2+1$,线性)——症状是”跑得飞快但答案错”,而不是 TLE。错误率 $n=14$ 时 99.91%。这条与编译选项无关,是纯逻辑错误。
⚠️ 模拟题两个”大数据反而测不出”的错法:「转向后立刻多走一步」错误率 $n\le4$ 14.99% → $n\le16$ 5.14% → 大数据 0.07%;「漏判越界上界」是 47.51% → 56.84% → 18.60%,非单调。而「统计步数而非不同格子」是 58.73% → 91.79% → 99.40%,随规模单调上升。三档规模缺一档就会得出相反的结论。
打表找规律的可行规模:$n\le11$。 暴力枚举全排列打错排数表,$n=11$ 要 1.3841 秒(不开 O2;开 O2 是 0.2422 秒),而 $n=12$ 要 16.8872 / 3.0283 秒——考场上打到第 11 项就该停手,而 11 项足够看出规律(找到递推后算 $D(20)$ 只要 0.000000 秒)。
前置:
basic-series/03-sorting-and-greedy.md —— 第 03 讲末尾埋的钩子「想到贪心先花两分钟找反例」,本讲 §4.1 的”打表找规律”是同一件事的正面用法。basic-series/01-prefix-sum-difference.md —— 枚举的常见搭档。gesp3-07-enumeration.md、gesp3-08-simulation.md)已经讲过什么是枚举、什么是模拟,以及最基本的循环嵌套写法。本讲不重抄那一层,从”枚举什么”和”怎么把题面变成状态”开始。适合对象:准备 CSP-J 复赛的同学(本讲是收益最高的一讲);CSP-S 选手可跳过 §3.4 的基础实现技巧,重点看 §2.3 子集枚举与 §4。
本讲不讲(只指路):