在算法竞赛中,评测机给你的时间通常只有 1 秒 (1.0s)。 你的任务不是写出“能算出答案”的代码,而是写出“在 1 秒内算出答案”的代码。
4.1 什么是时间复杂度 (Big O)?
我们不谈复杂的数学定义,只谈**“计算机要做多少次基本运算”**。我们用 来表示程序在最坏情况下的运算量级。
假设输入数据规模是 (比如数组里有 个数):
- 常数级:
无论 是多少,运行时间不变。
int a = v[10]; // 访问一次数组 int b = n * (n + 1) / 2; // 公式计算 - 线性级:
通常是一个
for循环。如果 ,大约计算 次。for (int i = 0; i < n; i++) { ... } - 平方级:
通常是双重嵌套循环。
⚠️ 危险:如果 ,运算量就是 (一亿次),这在 1 秒内已经很勉强了。for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) { ... } - 对数级: 非常快!通常出现在二分查找或 Set/Map 操作中。 即便 (天文数字), 也只有 60 左右。
- 线性对数级:
这是排序 (
sort) 的标准复杂度,也是大多数进阶算法(如分治)的复杂度。它比 稍微慢一点点,但远快于 。
4.2 黄金法则:1 秒 次运算
这是算法竞赛中最重要的经验公式:
现代评测机(C++)在 1 秒内大约能执行 到 次基本运算。
- 保守估计: 次(绝对安全)。
- 极限操作: 次(可能会 TLE,取决于常数大小)。
如何判断你的算法会不会 TLE? 把题目给出的最大 代入你的公式,算出结果,看看有没有超过 。
案例演练:
题目给定 ()。
- 你想写冒泡排序 (): 运算量 。 。结论:绝对 TLE,不用写了,换算法。
- 你写
std::sort(): 运算量 。 。结论:稳过,轻松 AC。
4.3 看菜吃饭:根据数据范围猜算法
这是高手解题的第一步。看到 的范围,你就大概知道出题人想考什么算法了。
| 数据规模 () | 允许的时间复杂度 | 常见算法/解法 |
|---|---|---|
| 或 | 状压 DP、DFS 暴搜 | |
| 或 | Floyd 最短路、区间 DP | |
| 朴素 DP、图的邻接矩阵、简单图论 | ||
| std::sort、堆、线段树、二分查找 | ||
| 双指针、KMP、并查集、线性筛素数 | ||
| 判断素数、分解质因数 | ||
| 最大公约数(GCD)、快速幂、数位DP |
💡 记忆技巧: 只要看到 (最常见的范围),哪怕题目再怪,你的解法大概率就是 排序 或者 二分,或者是 的贪心。
4.4 空间复杂度:别让内存爆炸
除了时间限制 (Time Limit),还有内存限制 (Memory Limit),通常是 128MB 或 256MB。
怎么算? 主要看你开了多大的数组。
int占用 4 字节 (Bytes)。long long占用 8 字节。char占用 1 字节。
常用单位换算: 1 MB Bytes。 256 MB Bytes。
如果你开了一个 int a[N]:
- (一百万): —— 非常安全。
- (一千万): —— 安全。
- (一亿): —— 危险!MLE (Memory Limit Exceeded)。
⚠️ 全局变量 vs 局部变量: 竞赛中建议把大数组开在
main函数外面(全局变量)。因为局部变量(在函数内部)存放在栈 (Stack) 区,空间很小(通常只有几 MB),开大数组会直接导致 Stack Overflow (爆栈) 运行时错误。
4.5 常见常数优化(玄学优化)
有时候你的复杂度是对的,但还是 TLE 了,可能是“常数”太大。
- 输入输出:记得用
ios::sync_with_stdio(0); cin.tie(0);(见第 2 章)。 - 取模运算:取模 (
%) 是很慢的操作,尽量减少取模次数。 - 位运算:乘 2 用
<< 1,除 2 用>> 1,判断奇偶用& 1(虽然现代编译器会自动优化,但手写位运算在某些题目仍有奇效)。 endlvs\n:永远不要在循环里输出endl,它会强制刷新缓冲区,极其慢。请使用\n。
🧠 本章小结与练习
核心思想: 动手写代码前,先看 ,再算 ,最后定方案。
练习作业(思考题): 假设你在做一个题目,输入 个点。
- 如果 ,你需要求任意两点之间的距离,你能用双重循环算出所有距离吗?(计算量 可以)
- 如果 ,你还能用双重循环吗?(计算量 绝对不行,必须想 或 的解法)。
掌握了“算命”的技巧,下一章我们将学习最朴实无华、但也是最容易得分的战术——怎么优雅地暴力破解?