【信奥业余科普】C++ 的奇妙之旅 | 29:别让 TLE 和 MLE 偷走你的分——复杂度估算与数据范围速查
在前面的文章中,我们学习了文件重定向操作 freopen。有了它,我们的代码就可以规范地在评测机上进行输入和输出。 但在实际的信奥比赛(如 GESP、CSP-J/S)中,光是写出“能运行出正确答案”的代码是不够的。每道题目都对资源有着极其严格的限制,通常表现为: - 时间限制:例如 1.0s(1.0 秒) - 空间限制...
已为您筛选出属于「信奥业余科普」核心专栏下的所有技术博文与考级解析
在前面的文章中,我们学习了文件重定向操作 freopen。有了它,我们的代码就可以规范地在评测机上进行输入和输出。 但在实际的信奥比赛(如 GESP、CSP-J/S)中,光是写出“能运行出正确答案”的代码是不够的。每道题目都对资源有着极其严格的限制,通常表现为: - 时间限制:例如 1.0s(1.0 秒) - 空间限制...
在前面的文章中,我们介绍了 C++ Standard Template Library (STL) 中的通用算法库 <algorithm。掌握了这些常用算法后,我们已经能够写出非常高效的数据处理代码。 然而,在实际参加信奥竞赛(如 CSP-J/S、NOIP 等)时,仅仅写出逻辑正确的代码是不够的。信奥比赛有一条极其严格...
在前面的文章中,我们介绍了 C++ STL 中的各种容器。在实际编写程序和参加竞赛时,仅存储数据是不够的,通常还需要对数据进行各种操作,例如: - 将学生的成绩按照从高到低排序(sort); - 去掉数组中重复的数据(unique); - 在排好序的数据中用二分法快速查找某个数(lowerbound / upperbo...
上一篇文章我们介绍了 set 和 multiset,它们通过底层的红黑树实现自动去重、自动排序,以及在 时间内进行查找的机制。 但是,set 只能存放单一的元素(也就是 键 Key)。在很多实际的信奥问题或开发场景中,我们需要的是一种“对应关系”。例如: - 给你一个学生的名字(Key),你需要...
上一篇文章我们拆解了 deque 双端队列,并以此告别了“序列容器”的世界。在之前的文章中,无论是 vector、stack、queue 还是 deque,元素都是按照我们 插入的顺序 排列的。如果想要查找某个元素是否存在,在没有排序的情况下,我们只能从头到尾挨个找,时间复杂度是 。 如果我们需要一个容器,...
上一篇文章介绍了 stack 和 queue,它们本质上是容器适配器,底层默认使用的容器就是 deque。我们提到 deque 能同时做到头尾操作 和下标随机访问 ,比 vector 灵活得多。 这篇文章就来拆解 deque 的内部结构,看看它是怎么做到这些的。
上一篇文章我们介绍了 vector 动态数组。vector 支持末尾追加、下标随机访问、中间插入删除等操作,功能非常灵活。 既然 vector 已经足够通用,为什么 STL 中还要专门提供 stack(栈)和 queue(队列)? 这就引出了软件工程中一个重要的设计思想:主动限制权限。在"撤销操作"或"排队处理"等场景...
上一篇文章介绍了 STL 的整体设计思想——容器、迭代器、算法三层架构。从本篇开始,我们将逐一深入 STL 中最常用的容器。 第一个要详细展开的,自然是 vector——信奥中使用频率最高的容器,没有之一。
在前面的二十篇文章中,我们从底层的"0和1"、变量的内存布局,一路讲到了函数、指针与引用。到目前为止,我们已经掌握了 C++ 中最基本的构件。 但在实际的软件开发中,如果每次遇到问题都要从零开始手写数组管理、排序算法,效率就太低了。为了解决这个问题,C++ 提供了一套功能强大的标准工具集——STL(Standard T...
上一篇文章中,我们深入理解了指针的设计原理——通过存储内存地址,实现函数间的高效数据共享。但我们也看到了指针的另一面:需要手动使用 和 & 进行解引用和取址操作,代码中符号密集,容易出错,可读性也会下降。 C++ 的设计者 Bjarne Stroustrup 在设计 C++ 时,为了在保留指针底层能力的同时提供一种更简...
在上一篇文章中,我们介绍了函数可以作为独立的黑盒处理数据,但这带来了一个棘手的物理限制:当我们把变量传递给函数时,系统默认执行的是“按值传递(Pass by Value)”。这意味着,如果传递的是一个体积巨大的数组,系统要在极短时间内完成全量的物理数据拷贝,不仅效率极低,更极其容易突破栈内存的容量上限,导致程序崩溃(栈...
在此前的旅程中,我们顺着程序的生命流水线,从存储单个数据(变量)一路走到了成规模的信息容纳仓库(数组),并使用判断与循环给程序注入了逻辑能力。 理论上,你完全可以把所有的代码统统塞进 main() 主程序里。但当代码量达到数千行时,这种做法会暴露出极其直接的致命缺点: 1. 代码极难维护且难以复用:各种计算任务糅杂在一...
在上一篇文章中,我们见识了“一维数组”。通过在物理内存中开辟一块连续的直线空间,结合底层的“首地址+偏移量”设计,一维数组将成批的散乱数据变得井井有条,成为了配合循环结构批量处理数据的绝佳工具。 可是,现实世界的数据并不总是像糖葫芦那样排成单独的一条直线。当我们需要记录一张划分了横纵行列的 Excel 电子表格、一张纵...
在上一篇文章中,我们了解了循环结构。它能够让计算机往复执行相同的指令,极大地节省了代码所占用的内存空间。 但循环只能重复执行“动作”。如果我们要用一段循环指令去验证千万条不同的数据,就会面临一个明显的阻碍:名称各异的独立变量,无法配合循环被机器自动挨个读取。这就引出了我们今天要探讨的话题:数组(Array)。
在上一篇文章中,我们了解了 if-else 判断语句。依靠底层“程序计数器(PC)”的强制跳转功能,程序能够在遇到分岔路口时做出各种方向选择。然而,如果我们要让程序计算从 1 加到 1000 的和,或者让程序连续处理百万个用户的数据,光靠一次性的判断语句显然是不够的。 这就是我们今天要探讨的话题:计算机是如何完成成千上...
在上一篇文章中,我们探讨了计算机底层二进制存储的规则,了解了“爆 int”数据溢出与浮点数精度丢失的原因,并简单了解了条件控制。不过,我们目前编写的程序还有一个明显的问题:代码只能从上到下按顺序执行,不会根据情况改变执行路线。 如果在之前分苹果的程序中,用户输入的小朋友数量是 0,程序在执行除法时就会因为除数为零而引发...
在第 11 篇文章中,我们提到 int、double 等数据类型本质上是向系统申请固定大小的内存空间。在第 12 篇文章中,我们看到整数除法(如 5 / 2)会舍弃小数部分,仅保留整数 2。 这些现象的根本原因在于:计算机内部依靠晶体管的高低电平处理数据,只能理解由 0 和 1 组成的二进制。 今天,我们将探讨不同数据...
在持续更新《计算机历史》与《C++ 的奇妙之旅》这两个致力于探讨底层运作机制与基础核心思想的科普系列之际,我关注了一下后台的阅读数据。 坦率地说,这让我产生了一点关于网络时代学习方式的困惑。 我个人非常喜欢,甚至要求我的孩子对“信奥科普系列”文章每篇都要仔细阅读给我总结。但这个系列的阅读量与讨论度,反而往往比不上我顺手...
在上一篇文章中,我们介绍了变量的概念,理解了程序是如何在内存中开辟“收纳空间”存放不同类型数据的。然而,如果一个程序只能在代码里写死固定的数字(比如永远只算 12 + 5),那它只具备计算器的单一计算功能,算不上灵活的软件或算法。 为了让程序能够根据现实情况动态处理问题,它必须具备从外部获取数据的能力,并在内部完成特定...
在上一篇文章中,我们剖析了“Hello, World!”背后的编译原理与程序的骨架结构。现在我们已经知道如何让程序通过屏幕和世界打招呼了。但真正的软件和算法绝不只是为了打印几行固定的文字,其存在的根本目的是为了处理数据。 要处理数据,首先要有一个地方存放数据。今天,我们就来解构几乎所有编程语言的基础:变量与数据类型。我...