1811 字
9 分钟
CSP-J/S骗分技巧
2026-01-05

在信息学竞赛的圈子里,流传着一本著名的神作《骗分导论》。所谓的“骗分”,不是指作弊或违反考场规则,而是在面对自己无法写出完美正解的题目时,利用竞赛的评测机制、数据范围和题目特性,尽可能多地搜刮“部分分”的算法艺术

CSP-J/S 的评测机制是按测试点给分。这意味着:即使你不会写 O(NlogN)O(N \log N) 的满分正解,只要你能用各种手段拿到 30 分、50 分甚至 80 分,积少成多,也能轻松锁定一等奖!

本文将为你系统梳理 CSP-J/S 考场上合法且极其高效的 “骗分”技巧全攻略


一、骗分基本功:吃透 Subtask 与分类拼凑#

CSP 题目的数据范围表格中,通常会将测试点拆分为若干个小块(Subtask)。这就是官方送给你的“骗分路线图”。

1. 暴力(DFS / 暴力枚举)打底#

任何题目,拿到手如果 20 分钟内没有正解思路,第一件事就是写暴力

  • N10    O(N!)N \le 10 \implies O(N!) 全排列枚举。
  • N20    O(2N)N \le 20 \implies O(2^N) 子集折半或 DFS 爆搜。
  • N100    O(N3)N \le 100 \implies O(N^3)O(N4)O(N^4) 多重循环。

原则:不要嫌弃暴力只有 20~30 分。四道题的暴力全部拿到,往往就已经接近一等奖分数线了!

2. 针对“特殊性质”单独写逻辑#

题目说明里经常包含类似这样的字眼:

  • “另外 20% 的数据,满足树退化为一条链”     \implies 当作一维数组处理。
  • “另外 20% 的数据,保证 Ai0A_i \ge 0     \implies 无需考虑负数,直接贪心。
  • “另外 15% 的数据,满足 K=1K=1     \implies 特殊单次询问,推导简单公式。

3. 用 if-else 拼凑得分(分段得分法)#

把不同 Subtask 的代码写在不同的函数里,在 main 函数中用 if-else 分流。这是最标准的“拼分”架构:

#include <bits/stdc++.h>
using namespace std;
int n, m;
bool is_chain() {
// 检查输入数据是否符合“链”的特征
return true;
}
void solve_dfs() { /* 20% 分数:暴力爆搜 */ }
void solve_chain() { /* 20% 分数:链的特殊性质 */ }
void solve_k1() { /* 20% 分数:K=1 的情况 */ }
void solve_rand() { /* 剩余数据:随机化 / 贪心 */ }
int main() {
cin >> n >> m;
if (n <= 10) {
solve_dfs();
} else if (is_chain()) {
solve_chain();
} else if (m == 1) {
solve_k1();
} else {
solve_rand(); // 拿不到全分,撞大运拿几分
}
return 0;
}

二、进阶骗分:启发式搜索与卡时技巧#

如果你只能写出暴力,如何让暴力程序跑得更快,从而“跨越”数据范围,骗到更多分数?

1. “卡时”大法(Time-based Exit)#

评测机单点限时通常为 1.0 秒(1000 ms1000\text{ ms})。如果你的搜索或随机算法可能会 TLE(超时),可以利用 <chrono>clock() 在超时前强行终止并输出当前找到的最优解

#include <iostream>
#include <chrono>
using namespace std;
auto start_time = chrono::steady_clock::now();
bool time_out() {
auto now = chrono::steady_clock::now();
double elapsed = chrono::duration<double>(now - start_time).count();
return elapsed > 0.92; // 超过 0.92 秒立刻停止,留 0.08 秒做收尾和输出
}
void dfs(int state) {
if (time_out()) return; // 随时检查时间
// 正常的 DFS 逻辑...
}

2. 贪心 + 随机打乱(Random Shuffle)#

在图论、背包、NP 难问题中,单纯的贪心可能会被构造数据卡掉。但是:“贪心不够,随机来凑”! 我们可以将输入数据的顺序随机打乱(std::shuffle),然后运行 100 次贪心,取所有运行结果中的最优值:

int best_ans = 0;
while (!time_out()) { // 在限时内反复跑
shuffle(a.begin(), a.end(), rnd); // 打乱输入顺序
int current_ans = greedy_solve();
best_ans = max(best_ans, current_sum);
}
cout << best_ans << endl;

真实战绩:这种“随机化贪心”在许多复杂的求解/优化题中,经常能把 30 分的暴力骗到 70~80 分甚至奇迹 AC!

3. 模拟退火(Simulated Annealing)与爬山法#

对于求解极值(最大值/最小值)的几何、图论或组合优化问题,如果在考场上想不到 DP 或高级数据结构,直接写一个模拟退火模板。调好参数后,大概率能刷过大半测试点。


三、奇技淫巧:打表、猜输出与特例骗分#

1. 本地打表(Precomputation)#

当题目要求输出某个特定数学序列或小范围状态(例如 N50N \le 50 的某种方案数),但你的算法运行一次需要 10 秒(本地能跑,提交会 TLE):

  • 在本地电脑上运行你的暴力程序,把 N=150N=1 \dots 50 的答案全部算出来。
  • 复制这些结果,在代码里硬编码写成一个全局数组:
    int ans[] = {0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...};
    int main() {
    int n; cin >> n;
    cout << ans[n] << endl; // O(1) 瞬间输出
    }

2. 猜测特例输出(无脑偏分)#

当题目时间所剩无几(比如只剩最后 3 分钟),实在写不出任何代码时:

  • 如果题目问“是否存在解,不存在输出 -1”:直接全输出 -1。评测数据中往往有 10%~20% 的点是没有解的!
  • 如果题目问“YES 或 NO”:全输出 YES 或全输出 NO(通常能拿到 10%~30% 的分数)。
  • 如果题目求最小值/最大值:输出 01 或题目中给定的某个边界极值。

3. 样例反推(打表找规律)#

如果题目看起来像数学题或博弈论,先写个小暴力把 N=120N=1 \sim 20 的输出答案打出来,观察数列:

  • 是不是等差/等比数列?
  • 是不是斐波那契数列?
  • 是不是 N % 4 == 0 时先手必胜? 很多时候,甚至不需要严谨的数学证明,找出了规律,写出单行公式就能直接 AC

四、骗分的底线与避坑指南#

骗分虽然快乐,但必须在规则允许的范围内进行。以下是考场绝不能触碰的红线:

  1. 严禁攻击评测机/读取测试数据
    • 不要尝试读取根目录或系统文件,不要试图打开同目录下的 .in 文件(除非是题目的 freopen),这会被系统判定为违规作弊,取消成绩!
  2. 警惕 RE(运行错误)导致 0 分
    • 骗分代码如果写得太粗糙,可能会导致数组越界除以零。遇到 RE,该测试点直接 0 分。写暴力时数组同样要开够。
  3. 不要让“骗分”影响了“正解”
    • 如果你在某道题上有 80% 的把握写出正解,请优先写正解!“骗分”是在正解无望情况下的退而求其次,切勿本末倒置。

五、总结:骗分的终极心法#

信息学竞赛不是满分通关游戏,而是一场分数的争夺战

  1. T1 / T2:追求稳拿正解(100 + 100)。
  2. T3 / T4:正解不会写?暴力 30 分 + 特殊性质 20 分 + 随机化/卡时 20 分 = 70 分

当你在考场上学会了把每道题的“渣渣分”搜刮干净,你会发现一等奖的门槛其实并没有想象中那么高。祝你在 CSP-J/S 考场上逢考必过,分分必争!