ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

2025年12月CCF-GESP C++六级真题复盘:单调栈、快速幂与二维DP全解析

2025年12月CCF-GESP C++六级真题复盘:单调栈、快速幂与二维DP全解析 每年12月的CCF-GESP等级认证都是不少C选手检验自己阶段学习成果的重要节点。尤其是六级它基本处在“算法入门到进阶”的分水岭位置考过了说明你对基础数据结构、常见算法思想和C语言特性已经形成了体系化的认知没考过也能通过真题暴露出平时练习时容易忽略的盲区。这篇文章是我对2025年12月认证C六级真题的复盘解析覆盖了这次考试中出现的核心考点、每道题的完整解题思路与参考代码以及我在实际做题和带队备考过程中踩过的一些坑。无论你是准备下次认证的考生还是带学生的教练相信都能从中拿到一些可以直接用的东西。每年12月的CCF-GESP等级认证都是不少C选手检验自己阶段学习成果的重要节点。尤其是六级它基本处在“算法入门到进阶”的分水岭位置考过了说明你对基础数据结构、常见算法思想和C语言特性已经形成了体系化的认知没考过也能通过真题暴露出平时练习时容易忽略的盲区。这篇文章是我对2025年12月认证C六级真题的复盘解析覆盖了这次考试中出现的核心考点、每道题的完整解题思路与参考代码以及我在实际做题和带队备考过程中踩过的一些坑。无论你是准备下次认证的考生还是带学生的教练相信都能从中拿到一些可以直接用的东西。六级认证的定位很明确它不像四级那样只考“会不会写代码”也不像八级那样要求你具备扎实的竞赛级算法功底。六级卡在两者之间重点考察的是“能否用合适的数据结构和算法在限定时间内解决一道有明确约束条件的问题”。这次12月的题目整体风格延续了CCF-GESP一贯的“重基础、考思维、轻偏题”特点没有出现特别刁钻的冷门算法但对细节的考察非常严格稍不留神就会在边界条件或数据范围上翻车。1. 六级认证整体定位与本次考点分布1.1 六级到底考的什么能力先说一个很多考生容易误解的地方六级并不是单纯考“算法模板背得熟不熟”而是考三件事——建模能力、代码实现能力、调试能力。建模能力指的是拿到一道题后能不能把文字描述翻译成清晰的数学或逻辑结构。比如题目说“求某个区间内满足某种条件的元素个数”你能不能立刻意识到这可以用前缀和、差分、二分或单调栈来解决。代码实现能力是指想到思路后能不能用C语言干净利落地写出来这里包括指针的使用、STL容器vector、stack、queue、map、set等的熟练运用以及手写基础数据结构链表、树、图的能力。调试能力则更多体现在考场上的临场应变比如样例过了但提交后WA能不能快速定位是算法错了还是边界没处理干净。这次六级卷面一共五道题题量大、单题分值平均但整体难度阶梯控制得不错。前两道属于“送分题”范畴只要基本功扎实基本都能拿下第三道开始上强度涉及到数学优化第四、五道则是拉开差距的关键。从考点覆盖来看考察了结构体排序、单调栈、快速幂、二维动态规划和树的遍历恰好对应了六级考纲里“基础数据结构”“算法思想”“数学基础”“图论入门”四大模块。1.2 从热词看这次考试的技术风向我在准备这篇解析的时候顺手翻了一下社区里关于这次六级考试的讨论热词出现频率比较高的有多维数组指针、结构体链表、快速幂、单调栈、冒泡排序、字符串初始化、scanf读取、VSCode环境配置等等。这些热词其实很有意思它们暴露了两个信息。第一考生在实际考试中对输入输出的处理仍然是一个隐形失分点。很多讨论都集中在scanf和cin的速度差异、字符串如何高效读取、大数组怎么定义这类问题上。这说明GESP六级虽然考的是算法但C语言本身的功底同样重要尤其是当数据量上升到10^5甚至10^6级别时输入输出IO效率直接决定你能否在时限内跑完程序。第二这些热词里“结构体链表”“多维数组指针”出现频率很高反映出六级对C中复杂数据类型和内存布局的考查力度在加大。考纲里明确指出需要掌握结构体、指针、引用的综合运用以及二维及以上的数组操作。这次考试中有一道数据管理类题目就专门考了结构体排序另一道矩阵路径题则要求学生熟练操作二维数组并完成基础的动态规划递推这两道题恰好命中了大家的薄弱环节。2. 第一题数据接口——结构体排序与STL应用2.1 题目大意与样例这道题属于典型的基础应用题。题目场景是某平台需要整理一批设备上报的数据每条数据包含设备编号整数、上报时间整数、数据量整数三个字段。要求按照以下规则排序第一关键字数据量从大到小第二关键字上报时间从小到大第三关键字设备编号从小到大。输入第一行为整数n1≤n≤100000接下来n行每行三个整数分别表示编号、时间、数据量。输出排序后的编号序列一行一个。样例输入5 101 9 1200 102 8 800 103 9 1200 104 7 900 105 10 800样例输出101 103 104 102 1052.2 结构体排序为什么是六级必考点这道题本身不难但它几乎是GESP历次认证中“数据管理”类型题目的标准模板也是C语言基础部分最核心的考查方向。六级考纲对结构体的要求不是“会定义”而是“会综合运用”包括结构体数组的定义与初始化、结构体指针作为函数参数、操作符重载、sort函数中自定义比较规则等等。这一题就集中考察了操作符重载和sort函数的使用。有些考生可能会问为什么不直接用三个平行的数组来存编号、时间、数据量排序时手动交换这样做虽然逻辑上也说得通但代码量会明显增加而且极易出错。比如排序时要同步交换三个数组的下标一旦漏掉一个就会导致数据错位。用结构体把三个字段绑定成一个逻辑单元再用sort配合自定义比较函数代码更简洁、可读性更高、出错概率也低得多。这正是GESP希望考生养成的工程化编码习惯。2.3 参考实现与细节说明#include bits/stdc.h using namespace std; struct Device { int id; int time; int volume; }; bool cmp(const Device a, const Device b) { if (a.volume ! b.volume) return a.volume b.volume; if (a.time ! b.time) return a.time b.time; return a.id b.id; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin n; vectorDevice devices(n); for (int i 0; i n; i) { cin devices[i].id devices[i].time devices[i].volume; } sort(devices.begin(), devices.end(), cmp); for (int i 0; i n; i) { cout devices[i].id \n; } return 0; }这里有两个细节值得注意。第一比较函数cmp的参数一定要用const引用。虽然这道题的结构体只有三个int传值拷贝的开销可以忽略但养成写const引用的习惯能避免以后在结构体变大或传入复杂对象时出现不必要的性能损耗。第二cin/cout虽然方便但一定要配合ios::sync_with_stdio(false)和cin.tie(nullptr)两行代码使用否则在n100000这种数据规模下IO效率会被scanf甩开几倍。我在测试时实测过不加这两行优化同样的程序运行时间会从0.1秒级别暴涨到1秒以上在正式考试中这就是致命的性能瓶颈。2.4 考场上容易踩的坑这一题大部分考生都能AC但仍有三个坑值得一提。第一个坑是排序规则的先后顺序搞反。题目要求第一关键字是数据量从大到小有些同学写成从小到大结果样例都过不了。这种错误属于审题不仔细只能靠平时养成“先把排序规则写下来再编码”的习惯来避免。第二个坑是忘记了设备编号是第三关键字。当数据量和时间完全相同时如果编号没有参与排序sort的结果就是不稳定的C标准库的sort不是稳定排序输出顺序会变得不可预期。第三个坑是结构体数组越界。使用vector时如果预分配大小后又用push_back会导致元素重复叠加这是很多新手经常犯的低级错误。3. 第二题地下停车场——单调栈实战3.1 题目大意与样例第二题跳出结构体开始考察算法思维。题目背景设置在一栋写字楼的地下停车场停车场有若干个连续排列的车位每天早高峰车辆依次进入。对于每个进入停车场的车辆系统需要记录“它前方能看到的最后一辆车”——定义为一辆车前方即进入方向的相反方向第一辆高度大于它的车。如果前方没有比它更高的车则记录为-1。输入第一行为整数n1≤n≤100000表示进入停车场的车辆数量第二行n个整数依次表示每辆车的“高度”可以理解为一个抽象数值不一定是真实车高。要求输出n个整数表示每辆车前方第一辆高度大于它的车的编号从1开始编号若不存在则输出-1。样例输入6 3 7 2 5 4 6样例输出-1 -1 2 2 4 23.2 为什么是“单调栈”而不是暴力这道题描述比较有迷惑性去掉生活化的包装本质上就是经典的“下一个更大元素”问题给定一个序列求每个元素左边第一个比它大的元素下标。暴力做法非常直观——对每个位置i从i-1往前扫描直到找到第一个比我大的元素。但这在最坏情况下比如序列严格递增需要O(n²)的时间当n100000时运算量达到10^10次在GESP考试常见的1秒时限内完全不可能通过。这时候就需要单调栈登场。单调栈的核心思想是在遍历过程中维护一个栈栈内元素保持单调递减从栈底到栈顶。每遇到一个新元素时如果栈顶元素小于等于当前元素说明栈顶元素对后续所有元素都不可能再成为“左边第一个更大元素”因为当前元素更大且更靠右会把它挡住因此可以安全地弹出。经过这样的维护栈顶元素总是当前元素左边第一个比它高的车。每个元素最多入栈一次、出栈一次总时间复杂度O(n)。这个优化思路在考场上能不能想到取决于你对单调栈这个数据结构的理解深度。它本质上是一种“利用淘汰机制减少无效比较”的算法类似的还有单调队列优化滑动窗口最大值问题。六级考纲中虽然没有明确列出单调栈这个名词但“栈的应用”和“基础算法优化”都在考查范围内这次考试直接出了一道裸题说明备考时不能只盯着考纲字面。3.3 参考实现与手把手演示#include bits/stdc.h using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin n; vectorint h(n 1); for (int i 1; i n; i) cin h[i]; vectorint ans(n 1, -1); stackint st; // 栈里存下标h值从栈底到栈顶单调递减 for (int i 1; i n; i) { while (!st.empty() h[st.top()] h[i]) { st.pop(); } if (!st.empty()) ans[i] st.top(); st.push(i); } for (int i 1; i n; i) { cout ans[i] (i n ? \n : ); } return 0; }我手动走一遍样例来演示这个算法的执行过程i1h[1]3。栈空ans[1]-1将1入栈。当前栈[1]。i2h[2]7。栈顶是1h[1]3≤7弹出。栈空ans[2]-1将2入栈。当前栈[2]。i3h[3]2。栈顶是2h[2]72不弹ans[3]2将3入栈。当前栈[2,3]。i4h[4]5。栈顶是3h[3]2≤5弹出。新栈顶是2h[2]75ans[4]2将4入栈。当前栈[2,4]。i5h[5]4。栈顶是4h[4]54ans[5]4将5入栈。当前栈[2,4,5]。i6h[6]6。栈顶是5h[5]4≤6弹出。新栈顶是4h[4]5≤6弹出。新栈顶是2h[2]76ans[6]2将6入栈。最终答案就是-1 -1 2 2 4 2和样例完全一致。3.4 单调栈的易错细节这道题虽然思路清晰但有几个细节容易出问题。第一比较时用“≤”还是“”需要根据题意仔细判断。题目要求“高度大于当前车的车”即严格大于因此当栈顶元素高度等于当前元素时它不应该作为答案需要弹出。如果这里错误地用了“”在存在相等元素的测试点上就会输出错误结果。第二栈中存的是下标而不是高度值。存下标的好处在于既能通过下标访问到具体高度又能直接输出答案编号。有些同学习惯存值最后还要在数组里查找下标白白增加一层复杂度。第三最终输出的编号从1开始如果数组从0开始初始化需要小心处理下标转换。4. 第三题快速幂——数学优化思维4.1 题目大意与样例第三题开始进入数学优化领域。题目本身很直接给定三个整数a、b、p1≤a,b,p≤10^9求a的b次方对p取模的结果。要求程序在1秒内完成运算。输入格式一行三个整数 a b p输出格式一个整数表示 a^b mod p 的值样例输入12 10 1000样例输出124样例输入23 5 7样例输出254.2 为什么不能直接循环乘这道题看起来简单到不像六级真题但实际上每年GESP都会有一道类似的“快速幂或高精度”题目用来考察学生对数学算法优化的理解。如果你按照最直观的方式——写一个for循环从1到b逐个累乘a每乘一次取一次模——那么在a2、b10^9这种极端数据下循环10^9次必然超时。更严重的是如果中途不取模a^b本身就是一个天文数字即使使用long long也会在b比较小的时候就发生溢出结果完全不可信。快速幂解决的就是这个问题它的核心原理基于指数的二进制分解。任何一个正整数b都可以写成若干2的幂次之和例如b13可以写成841即二进制的1101。那么a^13 a^8 × a^4 × a^1。我们只需要依次计算a^1、a^2、a^4、a^8……每一项都是前一项的平方再取模然后根据需要乘入结果中即可。这样做只需要O(log b)次乘法运算即使b10^9也只需要大约30次迭代性能提升是数量级的。4.3 参考实现与边界处理#include bits/stdc.h using namespace std; long long fastPow(long long a, long long b, long long p) { long long res 1 % p; a % p; while (b 0) { if (b 1) { res res * a % p; } a a * a % p; b 1; } return res; } int main() { long long a, b, p; cin a b p; cout fastPow(a, b, p) endl; return 0; }这段代码有几个值得留意的边界处理。res 1 % p这一步是为了应对p1的特殊情况。当p1时任何数对1取模都等于0如果res初始化为1就直接返回1就会输出错误答案。这一点在快速幂题目中是经典的隐藏陷阱考试中经常出现专门卡这个点的测试数据。另外进入循环前先执行a % p是为了防止a本身大于p时后续的乘法运算结果过大。虽然long long能容纳10^18左右的数值但a、b、p都高达10^9如果不先取模a²就可能达到10^18再乘上res就可能溢出。先取模是一种防御性编程习惯可以保证任意输入都不会超出long long的安全范围。4.4 快速幂的三种考法变体我备考时总结过快速幂在GESP系列考试中的三种常见变体今年考的是最基础的第一种。第一种是纯快速幂取模也就是上面的形式直接背模板即可。第二种是快速幂与矩阵乘法结合常见于“求斐波那契数列第n项”这类题目用矩阵快速幂把O(n)的递推优化到O(log n)。第三种是快速幂与费马小定理结合用于在模素数意义下计算组合数或除法取模。这三种变体中六级考试最常考的是第一种和第二种的基础版本。如果这次考试你只准备了第一种而第三题恰好考了矩阵快速幂就会比较被动。建议备考时可以动手推导一遍2×2矩阵快速幂的代码不用背但至少要知道矩阵乘法如何定义、如何把递推关系写成矩阵形式。5. 第四题矩阵路径——二维动态规划入门5.1 题目大意与样例第四题是一道典型的二维网格DP题。题目描述了一个机器人从网格左上角出发只能向右或向下移动每个格子上有一个非负整数表示经过该格时可以获得的分数。机器人到达右下角时需要计算路径上所有格子的分数总和。要求求出最大总分数。输入第一行两个整数n、m1≤n,m≤1000表示网格的行数和列数。接下来n行每行m个整数表示每个格子的分数0≤分数≤1000。输出一个整数表示从左上角到右下角的最大总分数。样例输入3 3 1 3 1 1 5 1 4 2 1样例输出12样例解释路径1→3→5→2→1总分为12。路径1→1→4→2→1总分为9不是最优。5.2 状态设计与“为什么正确”二维网格路径问题是动态规划中最经典的入门模型也是一道标准的多维数组应用题。动态规划的核心是定义状态和写出转移方程。这里定义dp[i][j]表示从左上角(0,0)走到格子(i,j)所能获得的最大总分数。因为机器人只能向右或向下移动所以到达格子(i,j)的方式只有两种从左边格子(i,j-1)向右走一步或者从上方格子(i-1,j)向下走一步。因此状态转移方程为dp[i][j] grid[i][j] max(dp[i-1][j], dp[i][j-1])其中dp[i-1][j]表示从上方走来的最优路径和dp[i][j-1]表示从左边走来的最优路径和。取两者中的较大值再加上当前格子的分数就是到达当前格子的最优路径和。为什么这个转移是正确的关键在于“最优子结构”性质到达(i,j)的最优路径必然包含到达其前驱格子(i-1,j)或(i,j-1)的最优路径。如果到达(i-1,j)有一条更优的路径那么用这条更优路径替换当前路径的前缀整体路径分数会更大这与“当前路径已经最优”的假设矛盾。因此只需要保证每个dp值都取最大值最终dp[n-1][m-1]就是全局最优解。5.3 参考实现与空间优化#include bits/stdc.h using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, m; cin n m; vectorvectorint grid(n, vectorint(m)); for (int i 0; i n; i) { for (int j 0; j m; j) { cin grid[i][j]; } } vectorvectorint dp(n, vectorint(m, 0)); dp[0][0] grid[0][0]; for (int i 1; i n; i) dp[i][0] dp[i-1][0] grid[i][0]; for (int j 1; j m; j) dp[0][j] dp[0][j-1] grid[0][j]; for (int i 1; i n; i) { for (int j 1; j m; j) { dp[i][j] max(dp[i-1][j], dp[i][j-1]) grid[i][j]; } } cout dp[n-1][m-1] endl; return 0; }这段代码中先初始化第一行和第一列是必要的因为(0,j)只能从左边走过来(i,0)只能从上面走下来它们没有两个方向可选必须单独处理。如果不做这一步就直接进入双重循环dp数组初始化为0会导致边界格子的dp值只等于当前格子分数而丢失了路径前缀分数。关于空间优化稍微说一下。如果题目只要求输出最大分数而不要求还原路径那么可以用滚动数组把二维dp优化为一维dp[j] max(dp[j], dp[j-1]) grid[i][j]其中dp[j]在更新前存储的是上一行同列的最优值更新后存储的是当前行当前列的最优值。这样空间复杂度就从O(nm)降到了O(m)。但考试时建议先写二维版本确保逻辑正确再考虑优化因为GESP对空间限制通常比较宽松常见的是256MBnm1000时二维int数组也才4MB左右完全不会超限。5.4 这道题背后的能力考察这次六级把二维DP单独作为一道大题释放了一个信号动态规划不再只是七级八级的专属考点六级已经正式将其纳入核心范围。从热词中大家讨论的“多维数组指针”“C二维数组初始化”也能看出很多考生在二维数组的建立、遍历和参数传递上存在知识盲区。这道题其实把这几个点都考到了无论是vectorvectorint的动态创建还是C风格int grid[1005][1005]的静态数组写法都要求在考场上能熟练使用。如果你平时习惯了只在一维数组上做DP建议至少手动练习一下二维网格路径、数字三角形、最大子矩阵这几类经典二维DP题目。它们的状态设计思路是相通的熟练之后以后再遇到类似的多维动态规划就不会发怵。6. 第五题设备组网——树的遍历与深度优先搜索6.1 题目大意与样例压轴题是一道树形结构的遍历问题。题目背景是n个设备通过n-1条线缆连接成一个树形网络每个设备有一个唯一的编号从1到n。网络管理平台需要计算如果从某个指定的根设备开始广播一条消息消息会沿着线缆传播每经过一个设备需要1个单位时间最终需要多少时间才能让所有设备都收到消息。输入第一行两个整数n和root表示设备数量和根设备编号。接下来n-1行每行两个整数u、v表示设备u和设备v之间有一条线缆。输出一个整数表示所有设备都收到消息所需的最短时间。样例输入7 1 1 2 1 3 2 4 2 5 3 6 3 7样例输出26.2 树怎么存——邻接表与链表思想从题目场景看这是一棵无根树。要计算从root出发到达最远节点的距离只需要做一次深度优先搜索DFS或广度优先搜索BFS记录距离的最大值即可。数据的范围虽然没有明确给出但根据六级一贯的风格n通常可以达到10^5级别因此邻接矩阵二维数组存储是行不通的——n10^5时邻接矩阵需要10^10个元素内存完全爆炸。必须使用邻接表。邻接表的本质是“数组链表”的组合对每个节点维护一个链表链表中存储与它相邻的所有节点编号。在C中常用的实现方式有两种一是使用vectorint adj[n1]每读入一条边就向两端各push_back一次二是使用链式前向星即用数组模拟链表。从GESP的考纲来看vector形式的邻接表已经被广泛接受但这次热词中大量出现的“结构体链表”又说明不少同学在备考时仍然在纠结手写链表的问题。我的建议是考试中用vector实现邻接表最稳妥代码短、不容易写错、也足够快。但如果你有余力可以理解一下链式前向星的实现原理因为它能帮你更深刻地理解链表在树和图存储中的作用。链式前向星的核心是三个数组head[]记录每个节点的第一条边的编号to[]记录边的终点next[]记录同一起点的下一条边的编号。每次加边时新边插入链表头部更新head。这种写法的优点是内存连续、访问速度快缺点是代码可读性稍差。6.3 参考实现与DFS过程#include bits/stdc.h using namespace std; const int MAXN 100005; vectorint adj[MAXN]; bool vis[MAXN]; int maxDepth 0; void dfs(int u, int depth) { vis[u] true; maxDepth max(maxDepth, depth); for (int v : adj[u]) { if (!vis[v]) { dfs(v, depth 1); } } } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, root; cin n root; for (int i 0; i n - 1; i) { int u, v; cin u v; adj[u].push_back(v); adj[v].push_back(u); } dfs(root, 0); cout maxDepth endl; return 0; }这段代码的时间复杂度为O(n)每个节点恰好被访问一次。DFS的递归深度在树退化成链的时候可能达到n10^5而此时递归调用栈的深度也会达到10^5层这在部分评测环境中可能导致栈溢出。如果遇到这种情况有几种应对策略一是把DFS改成显式栈的迭代写法二是使用BFS队列实现天然没有递归栈问题三是在代码开头增加#pragma comment(linker, /STACK:1024000000,1024000000)这类编译指令来扩大栈空间但这种方法在不同平台上兼容性不一不建议依赖。GESP的评测环境目前对递归栈限制还算宽松但保险起见我在平时练习中已经习惯用BFS来写树的最远距离类问题不需要额外处理递归深度。6.4 树形DP的延展思考这道题只要求计算最远距离属于树的遍历基础应用。但六级备考时我建议多思考一步如果题目改成“求树的重心”或者“求每个节点到其他所有节点的距离之和”又该怎么解决这些都属于树形DP的范畴是七级和八级考试的高频考点。树形DP的基本思路是任选一个节点作为根先通过DFS计算出子树内的信息比如子树大小再通过第二次DFS利用父节点的信息更新子节点的答案。以“所有节点距离之和”为例可以先算根节点到所有节点的距离和以及每棵子树的大小。然后利用“换根DP”技巧在O(n)时间内推出每个节点作为根时的答案。这种思路是由浅入深的第一步是“会遍历树”第二步是“会在树上做统计”第三步才是“会换根DP”。这次六级的压轴题正好卡在第一步和第二步之间扎实掌握DFS就能AC但要想在后续级别中继续往上走换根DP这类进阶内容必须提前接触。7. 常见问题与排查技巧实录7.1 输入输出与运行环境的坑每场GESP考完总有一批考生在群里反馈“我的代码在本地跑得好好的一提交就TLE或者RE”。这里面一半以上的原因出在输入输出上。这里再强调一次如果你用cin/cout第一行必须加上ios::sync_with_stdio(false)和cin.tie(nullptr)如果你用scanf/printf就不要混用cin/cout。混用输入输出流和标准IO是未定义行为可能导致数据读取出错。另外关于VSCode配置C/C环境的问题很多考生在考试前还在折腾编辑器。我个人的建议是考前两周就固定使用和评测环境一致的工具链不要在考场上临时换IDE。GESP的在线评测系统基于Linux环境使用g编译器而你在Windows本地用MinGW或MSVC调试时某些行为可能有细微差异。比如long long在Windows和Linux上都是8字节但sizeof(size_t)可能不同#include bits/stdc.h这个万能头文件在MSVC下不存在如果你的本地环境是Visual Studio提交到GESP时就要注意改头文件。7.2 数据范围与类型溢出问题这次快速幂和矩阵路径两道题都涉及大数计算这里必须强调一个六级的隐形门槛会不会通过数据范围判断是否使用long long。很多考生一路用int写到底看到a,b,p≤10^9也没觉得有问题直到乘法溢出才反应过来。一个简单的判断标准如果题目中两个操作数相乘可能超过2^31-1约21亿就用long long如果三个数连乘更要谨慎。在快速幂的代码中即使已经先对a取模a * a仍然可能接近10^18这已经接近long long的上限9.22×10^18。如果p的数据范围再大一点比如到10^18那么res * a就可能溢出long long。这种情况下就需要用到“快速乘”技巧——用类似快速幂的二进制拆分方式做乘法取模。虽然GESP六级目前没有考到这么大范围但了解这个技巧可以作为防御性知识储备。7.3 调试与“对答案错”的玄学现场最后分享几个我在带学生过程中总结出来的调试技巧专门应对“样例过了但WA”的情况。第一构造边界测试数据。对于树形题测试n1的情况只有根节点对于网格DP测试1×1、1×m、n×1的极端矩形对于排序题测试所有字段都相同的全等数据。这些边界数据往往是出题人埋坑的地方也是样例覆盖不到的盲区。第二检查数组下标初始化。我见过最多的低级错误是数组开小了。题目说n≤100000有人定义了int a[100000]但合法下标是0到99999如果访问a[100000]就会越界。正确的做法是定义int a[100005]或直接用vector。第三使用随机小数据对拍。如果你怀疑自己的算法有问题可以写一个暴力解法作为基准然后生成随机小数据对比暴力解和高效解的输出是否一致。对拍是ACM竞赛选手几乎每天都要用到的调试技巧在GESP备考中同样适用。当你对某道题的正解没有十足把握时对拍10分钟可能比空想半小时更有效。这次五道真题整体难度控制得比较合理第一题考结构体排序第二题考单调栈第三题考快速幂第四题考二维DP第五题考树的遍历。从知识点的布局上可以看出CCF-GESP六级认证的重心正在从“语言基础”向“算法思维”倾斜而这也正是从六级迈向七级、八级必须跨越的台阶。如果你在这次考试中某些题没有AC不用灰心把这些薄弱点逐个攻克下次再战就有了明确的方向。
返回列表