CCF CSP 认证第33次真题深度剖析:从暴力到优雅的算法跃迁
最近和几位正在备战CCF CSP 认证 的学弟学妹交流,发现一个普遍现象:拿到真题后,大家的第一反应往往是“先暴力写出来,能拿几分是几分”。这种思路在时间紧迫的考场上固然实用,但若止步于此,便很难触及认证考察的核心——计算思维与优化能力。第33次认证的几道题目,恰好为我们提供了绝佳的样本,来审视“暴力解法”与“优化思路”之间那道看似微妙、实则深刻的鸿沟。
这篇文章,我想和你一起跳出单纯“看题解、背代码”的备考模式。我们将以第33次CSP认证的几道典型题目为线索,深入代码的肌理,还原解题时的真实 思考 路径。你会看到,一次成功的优化,往往不是灵光一现,而是基于对问题本质的洞察、对数据特征的把握,以及对基础数据结构与算法的娴熟运用。无论你是初次接触CSP的新手,还是希望刷分冲高分的进阶者,理解这种“从暴力到优化”的思维进化过程,远比记忆十套满分代码更有价值。
1. 词频统计:从直观计数到空间换时间的艺术
第一题“词频统计”通常被视作送分题,但它的朴素解法与优化解法之间,清晰地展示了算法效率的第一个分水岭。题目要求统计n个文档中,m个特定词语出现的文档频数(有多少个文档包含该词)和总频数(在所有文档中出现的总次数)。
最直接的暴力想法是什么?为每个词语维护两个计数器,然后遍历每个文档的每个词,如果匹配到目标词,就更新计数器。伪代码逻辑大致如下:
- // 伪代码:最直观的暴力双重循环
- for 每个词语 w in [1, m]:
- doc_count[w] = 0
- total_count[w] = 0
- for 每个文档 d in [1, n]:
- for 文档d中的每个词 word:
- if word == w:
- total_count[w]++
- // 如何判断这个文档是否已经统计过?
- // 需要额外记录这个文档是否已为该词贡献过文档频数
cpp
这里立刻遇到一个棘手问题:在统计文档频数时,需要确保同一个文档对同一个词的贡献最多为1。暴力实现可能需要为每个词语维护一个bool数组,记录哪些文档已经统计过,这会让空间复杂度变得很高。
让我们看看官方满分代码的巧妙之处:
- #include <stdio.h>
- int main() {
- int n, m;
- scanf("%d%d", &n, &m);
- int L, num;
- int c[110] = {0}, cnt1[110] = {0}, cnt2[110] = {0}; // cnt1:文档数,cnt2:总次数
- for(int i = 1; i <= n; i++){
- scanf("%d", &L);
- for(int j = 1; j <= L; j++){
- scanf("%d", &num);
- c[num]++; // 临时数组,记录当前文档中词语的出现次数
- }
- for(int k = 1; k <= m; k++){
- if(c[k] > 0){
- cnt1[k]++; // 当前文档包含词语k,文档数+1
- cnt2[k] += c[k]; // 总次数累加当前文档中的出现次数
- }
- c[k] = 0; // 关键!清空临时数组,为下一个文档做准备
- }
- }
- // 输出结果
- for(int i = 1; i <= m; i++){
- printf("%d %d\n", cnt1[i], cnt2[i]);
- }
- return 0;
- }
cpp
这段代码的精髓在于那个临时数组c和每文档处理后的清零操作。它实际上采用了一种“逐文档处理、即时汇总”的策略:
- 空间换时间:使用一个大小为
m+1的临时数组c,在读取单个文档时,快速记录该文档内各词语的出现次数。由于m最大为100,这个开销很小。 - 解决文档去重:在处理完一个文档后,立即遍历
c数组。对于c[k] > 0的词语,说明它在本文档中出现过,那么cnt1[k](文档数)直接加1即可,完美避免了同一个文档重复计数的问题。 - 同步累加总频数:在同一个循环中,将
c[k]的值累加到cnt2[k](总次数)中。
这种做法的优势非常明显:
| 方法 | 时间复杂度 | 空间复杂度 | 核心难点 |
|---|---|---|---|
| 朴素暴力法 | O(n * L * m) | O(m) + 额外去重存储 | 需要为每个词维护文档访问记录,实现复杂 |
| 优化逐文档法 | O(n * L + n * m) | O(m) | 利用临时数组和即时处理,逻辑清晰,效率高 |
提示:在CSP认证中,第一题往往考察基本的数据处理和简单的优化思想。遇到需要统计“是否
&spm=1001.2101.3001.5002&articleId=158340306&d=1&t=3&u=8b8a036ddb4a4e5d9389b89ddd10a34e)