洛谷入门3循环结构刷题指南:for/while与嵌套循环
发布时间:2026/10/18 10:54:53 锦皓数字建站

1. 循环结构到底在学什么先弄清楚这关的关键门槛刷到“洛谷-入门3-循环结构3”的同学基本都刚跨过顺序结构和分支结构的坎。这个阶段最核心的任务就是彻底搞懂程序里“重复做某件事”的能力——也就是循环。我见过太多新手在这里卡住。倒不是语法有多难而是思维方式还没转过来。你之前写代码是“一步步往下走”到了循环这儿你得学会“让一段代码反复执行并且每次执行时状态都在变”。这种思维转变比记住for和while怎么写重要得多。洛谷的入门3循环结构题单恰恰就是为这个转变设计的。它的题目覆盖了循环的几大典型场景累加累乘、枚举遍历、嵌套循环、循环与条件分支的组合应用。你把这套题刷明白后续学数组、学函数、学算法都会轻松很多因为几乎所有后续内容都建立在循环之上。我自己的体会是循环结构这关过不过得去直接决定了你能不能从“会写代码”变成“能解决问题”。洛谷这些题目的价值在于它们不是那种“为了语法而语法”的题目每个题目背后都有一个真实的小问题场景比如算数列求和、判断质数、打印图案你需要把问题抽象成循环逻辑这种训练特别重要。2. for循环和while循环别急着二选一先搞懂各自的脾气2.1 for循环的经典形态与计数习惯在洛谷的循环题里for循环是绝对的主力。它的标准写法大家应该都很熟了for (初始化; 条件; 更新) { // 循环体 }但我想强调一个新手特别容易忽略的点循环变量的边界判断。这个问题看着小实际特别坑人。比如数字直角三角形那类题目如P5721经常要做类似for (int j 1; j i; j)这种嵌套循环有人就会把内层的边界写成j i导致每行少输出一个字符甚至整个图形错位。判断边界时我建议你先用具体数字代入当i 3时内层循环应该跑几次如果答案是3次那j 1到j 3就是对的而j 3只跑了2次。养成“带一个具体值进去走一遍”的习惯比死记边界规则有效得多。for循环的更新语句也值得留意。大部分题目是i但有些题需要步进2比如统计奇数可以用i 2。还有些题目要从大到小枚举比如求最大质因数写成for (int i n; i 1; i--)反而更自然。C里i和i单独用时效果一样但如果写在表达式里前者是先用后加后者是先加后用这个细节在复杂表达式里可能踩坑建议平时统一用i能少一点心智负担。2.2 while循环的适用场景当循环次数不确定时while循环的形态是while (条件) { 循环体 }它和for的本质区别在于for更适合“我知道要循环多少次”的场景while更适合“我不知道次数只知道什么时候停”的场景。一个特别典型的例子是P1720月落乌啼算钱斐波那契数列题目要求一直累加到某项但项数可能由输入决定或者需要边计算边判断是否达到目标。虽然用for也能做但如果你不知道明确的循环次数用while写起来更符合思维习惯long long a 0, b 1, c; int n; cin n; if (n 0) { cout 0 endl; return 0; } for (int i 2; i n; i) { c a b; a b; b c; } cout b endl;这里虽然用了for但如果你换个场景——比如“一直累加直到和大于10000”那while就明显更合适int sum 0, i 1; while (sum 10000) { sum i; i; }这样写循环条件本身就是“什么时候该停”的描述读代码的人一眼就知道意图。选for还是while我个人的判断标准很简单如果循环次数是由一个明确的计数变量控制的用for如果循环终止条件是某种状态用while。两者技术上完全可以互换但选对了能让代码更清晰调试也更省事。还有一种情况必须用到do-while但洛谷入门3里出现得不多。do-while的特点是“先执行一次循环体再判断条件”适合那些至少需要执行一次的场景。比如判断一个数至少要除以多少次2才能小于1这种题目用do-while就能少写一些初始化的逻辑。不过入门阶段你只要知道它存在就行真遇到题了能想起来用它就足够了。2.3 for和while选型速查表我整理了一个快速选型表刷题时可以直接对照。场景特征推荐结构典型题目示例已知循环次数按计数遍历forP5722数列求和需要嵌套打印图形for外层内层P5721数字直角三角形循环次数由条件决定whileP1307数字反转至少执行一次后判断do-whileP5726打分先读入再循环需要从尾部反向遍历fori--P1075质因数分解从大到小找看着简单但在实际做题中不少同学会栽在“该用对用错了结构”上面。结构选对了代码量至少省一半。3. 洛谷入门3循环结构核心题型拆解从读题到代码的完整思路3.1 累加与累乘类数列求和的边界陷阱数列求和在循环题里属于基础中的基础洛谷里像P5722这类题都会涉及。比如计算1 2 ... n很多人闭着眼就能写#include iostream using namespace std; int main() { int n; long long sum 0; cin n; for (int i 1; i n; i) { sum i; } cout sum endl; return 0; }但真正到了P1009阶乘之和这种题难度就上来了。它要求1! 2! ... n!如果每项都从1开始重新算阶乘外层是n次循环内层每次又是i次乘法整体复杂度是O(n²)。n小的时候没事n大了就明显变慢。更聪明的做法是利用递推关系第i项阶乘等于第i-1项乘以i。#include iostream using namespace std; int main() { int n; cin n; long long term 1; // 当前项的阶乘 long long sum 0; // 累加和 for (int i 1; i n; i) { term * i; // 第i项阶乘 第i-1项阶乘 * i sum term; } cout sum endl; return 0; }这其实就是递推思想的雏形。很多同学觉得“算法”是很遥远的东西其实在循环题里递推已经在无数次出现了。你能不能在循环中维护一个“当前状态”并通过更新得到下一步的状态这是比语法重要得多的能力。这类题我踩过的一个坑是数据类型。n达到20时20!大约是2.43×10¹⁸已经逼近long long的极限约9.22×10¹⁸了。如果你用int存数据溢出后会变成负数你怎么查都查不出逻辑错误。所以在洛谷做题时凡是涉及累乘、斐波那契、大数求和我第一反应就是用long long甚至unsigned long long或者高精度。这不是小题大做而是经验。3.2 质数与枚举类循环与条件的嵌套协作质数判断是循环结构里绕不开的经典。P5723质数口袋、P1075质因数分解这类题核心逻辑都是枚举判断。判断一个数n是否为质数朴素做法是从2枚举到n-1bool isPrime(int x) { if (x 2) return false; for (int i 2; i x; i) { if (x % i 0) return false; } return true; }但这里有个重要的优化其实只需要枚举到sqrt(x)。因为如果x有一个大于sqrt(x)的因数那么必然存在一个小于sqrt(x)的因数与之对应。比如x 36它的因数有2、3、4、6、9、12、18你检查到i 6 sqrt(36)时就已经能找到所有小于等于6的因数而9、12、18这些都可以由36/4、36/3、36/2得到。bool isPrime(int x) { if (x 2) return false; for (int i 2; i * i x; i) { if (x % i 0) return false; } return true; }注意这里有个隐蔽的坑i * i x在i很大时可能溢出int类型所以更稳妥的写法是i x / i避免乘法的溢出风险。P1075质因数分解那题恰好就能用这个优化从小到大枚举找到第一个质因数后输出另一个因数因为题目保证只有两个质因数代码能写得非常短。这类枚举题真正考察的不是你能不能写出循环而是你能不能利用数学性质缩小循环范围。这种意识是后续学算法的基础。3.3 数字拆分与反转循环里的取余与整除配合P1307数字反转是入门3里非常有意思的一道题。它的核心操作是通过反复取余和整除把数字一位一位“抠”出来。比如对123反转为321只需要int n, rev 0; cin n; while (n ! 0) { rev rev * 10 n % 10; n / 10; } cout rev endl;这个代码背后的原理是十进制数的位置表示法。n % 10拿到最低位rev * 10 最低位相当于把最低位拼接到rev的末尾n / 10去掉最低位。每一轮循环旧的数字减少一位新数字增加一位整个过程中位数守恒。这类题目还有变体比如统计某个数字在1到n之间出现了多少次P1980计数问题本质也是“不断取模、整除、判断”。我建议你把“取余拿到最低位整除去掉最低位”这个组合记牢因为后续学进制转换、回文数判断都是同一套操作。还有一个细节负数反转。洛谷的P1307明确要求负数也要处理比如-380反转成-83。很多同学会在负数这里卡住其实只需要先记录符号再对绝对值做反转最后把符号加上去就行。用循环方法时n % 10在C里对负数会得到负的余数比如-380 % 10 0还好但-83 % 10的情况就麻烦了所以最稳妥的就是先取绝对值处理完后再根据原始符号调整输出。3.4 嵌套循环与图形打印控制变量的配合逻辑图形打印类题目比如P5721数字直角三角形和P5725求三角形是嵌套循环的入门训练。这类题的核心套路是外层循环控制行内层循环控制列。以数字直角三角形为例输入n输出一个类似下面的图形1 2 3 4 5 6 7 8 9 10关键在于你既要控制行号和列号还要维护一个全局递增的数字计数器。行号用i列号用j数字本身用一个独立的变量cnt来记录#include iostream using namespace std; int main() { int n, cnt 1; cin n; for (int i 1; i n; i) { for (int j 1; j i; j) { cout cnt ; cnt; } cout endl; } return 0; }这里有一个思路转换之前你写循环控制变量i和j是用来计数的但在图形题里你可能还需要一个“内容变量”来记录实际要输出的东西。把控制变量和业务数据分开是我做这类题的最大心得。你不要试图让j既当列号又当数字那样会把自己绕晕。还有一类图形题需要输出空格和字符的组合比如打印等腰三角形。内层循环可能需要两段一段输出空格一段输出字符。这时候嵌套循环内部再拆成两个并列循环就很自然for (int i 1; i n; i) { for (int j 1; j n - i; j) { cout ; } for (int j 1; j 2 * i - 1; j) { cout *; } cout endl; }这种“空格字符”的组合逻辑在后续很多算法题包括动态规划的打印路径里都有影子。别觉得图形题只是花架子它对循环控制能力的训练是不可替代的。4. 从读题到提交一份完整的刷题实操记录4.1 以一个跨度稍大的题目为例我来完整演示一道题的解题流程就拿P1009阶乘之和来说吧。这道题在洛谷入门3里属于稍偏后的位置需要的不再是单一的循环语法而是组合应用。第一步读题明确输入输出。输入一个整数nn 50输出1! 2! ... n!。注意n的范围是50这远远超出了long long的表示范围50!大约是3×10⁶⁴所以洛谷这道题实际要求高精度算法。第二步思考方案。朴素方案是每项独立算阶乘再累加但这意味着要写高精度乘法和高精度加法对刚学完循环的新手来说直接上高精度有点陡。这里有个特别重要的经验循环结构阶段遇到大数据不要慌着去学复杂算法先想想能不能用循环配合数组来模拟手算过程。高精度本质上就是用数组存大数字然后循环处理进位和累加。洛谷入门3的题目不会让你真写个完整的大数模板但P1009这种题实际上已经把高精度的基础逻辑传给你了。第三步具体实现高精度简化版。如果你只做到n 20那long long还能扛得住但题目给到了50我们必须用数组模拟。核心思路用一个数组表示当前阶乘的每一位数字从低位到高位存储每次乘一个数时逐位相乘并处理进位然后把当前阶乘的每一位加到总和数组里同时处理进位。我曾用下面这段代码通过了P1009只展示核心部分#include iostream #include cstring using namespace std; const int MAXN 1000; int fact[MAXN], sum[MAXN]; void multiply(int k) { int carry 0; for (int i 0; i MAXN; i) { int cur fact[i] * k carry; fact[i] cur % 10; carry cur / 10; } } void addToSum() { int carry 0; for (int i 0; i MAXN; i) { int cur sum[i] fact[i] carry; sum[i] cur % 10; carry cur / 10; } } void printArr(int arr[]) { int pos MAXN - 1; while (pos 0 arr[pos] 0) pos--; for (int i pos; i 0; i--) { cout arr[i]; } cout endl; } int main() { int n; cin n; fact[0] 1; for (int i 1; i n; i) { multiply(i); addToSum(); } printArr(sum); return 0; }这段代码要冲到大数组的长度足够容纳结果。结果接近50!的时候位数大概是60多位MAXN 1000完全够用。但如果以后再做大数题建议根据题目范围动态计算数组长度别盲目开太大以免浪费空间。第四步提交测试。我提交时用的是标准输入输出洛谷的在线评测对代码格式没有特别要求只要程序能处理多组测试数据就行。这里有个小提醒如果你在本地测试正常但提交后WA答案错误先检查是不是忘记注释掉调试输出或者数据范围开小了。我遇到过一次在循环里顺手加了一个中间打印结果忘记删提交后输出多了一堆东西直接WA。4.2 调试循环代码的几个实用技巧我在刷洛谷循环题的过程中总结了一套调试方法比盲目加cout高效得多。第一小数据测试。把n设为1、2、3这种极小值手动推演一遍期望结果然后看程序输出是否一致。比如阶乘之和n3时结果应该是1! 2! 3! 9如果程序输出10那多半是初始化的fact[0]多算了一项。第二边界值测试。输入题目给的最大值看程序是否会在时间或内存上出问题。如果题目说n 10^9你还在用for (int i 1; i n; i)那大概率超时。循环题里遇到大范围数据一定要想想能不能用数学公式直接计算比如等差数列求和而不是老老实实累加。举个例子求12...nn10^9时循环会超时但用公式n*(n1)/2瞬间算完。循环不是万能的知道什么时候不用循环也是循环结构学习的一部分。第三输出中间状态。如果实在查不出问题可以在循环体里临时输出变量的值。比如for (int i 1; i n; i) { cout i i sum sum endl; // 调试用 sum i; }观察每一轮的变化往往能很快定位到问题。记得调试完删掉或注释掉这些语句。4.3 常见编译与运行错误速查洛谷提交时编译错误CE、运行时错误RE和答案错误WA各有各的坑。我整理了一个速查表错误类型常见原因排查方法CE编译错误变量名拼写不一致、缺少头文件检查本地是否编译通过别只盯着逻辑RE运行时错误数组越界、除零、栈溢出检查循环边界特别是i n和数组下标的关系WA答案错误边界条件写错、数据类型溢出、漏了特殊情况用极值测试和手算小数据核对TLE超时循环次数过多算法太慢思考是否需要数学优化或改用更高效的循环方式有一个隐蔽问题循环变量类型不匹配。如果你拿int i去和long long n比较C可能会做隐式转换意外导致死循环或提前退出。比如n是long long且很大i是int加到超过int范围时i会溢出循环就完全失控了。所以我的习惯是循环变量的类型和边界变量保持一致。5. 循环思维进阶从洛谷入门3延伸到后续算法之路刷完洛谷入门3的循环题目你会发现自己已经不知不觉接触到了好几类重要的算法思维雏形。递归、前缀和、双指针、高精度这些后面听起来很高级的概念其实在循环题里都埋下了种子。比如你用循环递推实现了斐波那契数列P1720这在后续动态规划里就是最基础的状态转移。你从枚举质数中学会了缩小搜索范围这在后续素数筛、数论算法里无处不在。你通过嵌套循环打印三角形理解了外层内层控制变量的配合后续二维数组的遍历、矩阵操作都是同样的逻辑在不同数据结构上的应用。可以说循环结构是这些高级主题的共同地基。我在刷完这些题之后的一个明显感受是代码思维开始从“写句子”变成了“写段落”。以前你写几行指令程序线性执行现在你开始有意识地组织一段会被反复执行的逻辑并且思考它如何随变量变化。这种组织能力是编程水平的一个分水岭。还给新手一个建议这些循环题尽量不要满足于“AC了就完事”。每道题可以尝试多种解法——比如一道累加题你分别用for、while、甚至递归各写一遍然后对比代码结构和运行效率。这听起来费时间但对思维的锻炼效果极好。尤其是for和while的互换练习能帮你把两种循环真正吃透遇到新题时就不会纠结“该用哪种”。至于更多技巧比如用位运算替代部分乘除操作、用前缀和优化区间查询这些暂时还不着急你现在要做的是把循环本身的逻辑练扎实。等进入洛谷的后续题单比如数组、字符串、排序你就会发现循环结构已经内化成了你写代码的本能那时候回头看入门3的这些题目会有一种“原来当时是在打地基”的恍然大悟。我个人刷完整个入门3之后最大的收获不是AC了多少道题而是学会了“在重复中找规律在规律中找突破口”。写循环的人本质上是在用一种压缩的方式表达重复劳动。想清楚这一点你的循环结构就算真正学通了。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。