C语言小白传之初见洛谷
洛谷 GESP 入门题复盘5 道简单题涉及题目B2087、B3834、B3845、B3922、B4067。一、B2087 与指定数字相同的数的个数读入顺序比比较逻辑更容易错题意读入 (N) 个整数再读入目标数 (M)统计这 (N) 个数中有多少个等于 (M)。先看一段能直接 AC 的代码#includestdio.hintmain(){intcount0,a,c,b[100]{0};scanf(%d,a);for(inti0;ia;i){scanf(%d,b[i]);}scanf(%d,c);for(inti0;ia;i){if(b[i]c){count;}}printf(%d,count);return0;}这段代码的流程是先读数字个数a再读a个整数存进数组b再读目标数c最后遍历b计数。核心判断只有一句if(b[i]c){count;}逻辑本身没有问题直接提交可以 AC。但新手翻车点通常不在比较而在读入。题面里的“第一行”“第二行”是给人看的数据实际可能带任意数量的空格甚至折行。按空白符依次scanf是更稳的做法。可改进的地方不影响 AC数组b[100]对本题 (N \le 100) 刚好够用但下标是0 ~ 99习惯上开大一点更保险比如b[110]变量名a、b、c分别表示“个数、数组、目标数”读代码时容易混换成n、arr、m可读性更好但这只是风格问题 {0}初始化其实不是必须的数组会被读入循环完整覆盖。二、B3834 长方形面积去重的最好方式是根本不产生重复题意给定面积 (A)问有多少对正整数 ((长, 宽)) 满足 (长 \times 宽 A)且长宽交换视为同一种长方形。先看一段能直接 AC 的写法也就是我一开始的写法#includestdio.hintmain(){ints,a,b,count0;scanf(%d,s);for(a1;as;a){bs/a;if(ba){if(s%a0)//保证b确实是个整数而不是因为某些原因舍去了小数{count;}}}printf(%d,count);return0;}这段代码的思路是枚举短边a用整数除法算出另一条边b s / a再用b a保证只统计“短边 ≤ 长边”的那一次最后用s % a 0确认a确实是因子。逻辑是对的但b这个变量其实可以省掉。因为在整数除法下b a等价于s / a a也就等价于a * a s。所以循环条件可以直接写成#includestdio.hintmain(){intA,ans0;scanf(%d,A);for(inti1;i*iA;i){if(A%i0)ans;}printf(%d\n,ans);return0;}这样不需要整数除法也不用担心b的取整问题代码更短结果完全一致。新手提醒去重不一定非要“事后除以 2”在循环设计阶段就把对称性消灭掉代码更短边界更少用i * i A而不是i sqrt(A)可以避开浮点数比较。三、B3845 勾股数三重循环能过但值得知道它为什么“险”题意统计满足 (a^2 b^2 c^2) 且 abcn的正整数三元组数量。最容易先想到的写法是三重循环枚举 (a)、(b)、(c)再判断等式#includestdio.hintmain(){inta,b,c,n,count0;scanf(%d,n);for(a1;an;a){for(ba1;(a*ab*b)n*n;b)//让第二个数每次从a后面开始不会重复同时循环条件是c^2小于n^2.{intc2a*ab*b;//在这写下一个我们想要的c^2for(cb1;c*cc2;c)//c^2c^2的适合已经可以结束了后续就没必要。{if(c*cc2)//当枚举出来的c^2等于我们想要的c^2的时候计数器可以加一同时跳出这个循环。{count;break;}}}}printf(%d,count);return0;}这段代码以下可以关注外层a从 1 到n内层b从a1开始保证 (a b)避免 ((3,4,5)) 和 ((4,3,5)) 被重复统计内层循环条件(a*ab*b) n*n直接保证了 (c^2 n^2)即 (c n)省掉了后面再判断 (c) 是否超范围找到c*c c2后立刻break同一个 ((a,b)) 不会重复计数。在本题 (n 1000) 的数据下这段代码可以 AC。但它有一个值得留意的隐患这样子算太复杂了有太多了多余的计算如果量级再大一点可能有超时的风险。更稳妥的做法是直接算出 (c)砍掉第三层循环#includestdio.h#includemath.hintmain(){intn,ans0;scanf(%d,n);for(inta1;an;a){for(intba;bn;b){intc2a*ab*b;intc(int)sqrt(c2);if(cnc*cc2)ans;}}printf(%d\n,ans);return0;}这里有两个关键点内层从b a开始保证 (a \le b)。因为 (a b) 时 (2a^2 c^2) 无整数解所以从a还是a1开始结果一样写a更贴近题面的 (a \le b)。先取整再平方回去验证。不要写c sqrt(c2)这类浮点相等判断精度误差可能导致误判。写成int c (int)sqrt(c2); if (c * c c2)更安全。新手提醒三重循环逻辑正确在本题数据下能 AC但它是在“枚举 (c)”而不是“算出 (c)”浮点数适合“估算”不适合“判定相等”完全平方数判断的常用套路是“取整后平方回去”循环条件里加a*a b*b n*n可以进一步缩小枚举范围如果以后遇到 (n) 更大的勾股数题优先考虑用数论公式生成本原勾股数而不是暴力枚举。四、B3922 小杨报数核心逻辑只有一句写对就不容易错题意小杨从 (1) 开始报数报到 (M) 的倍数时跳过不报。给定 (N) 和 (M)输出所有报出的数每个数占一行。参考写法#includestdio.hintmain(){intN,M;scanf(%d,N);scanf(%d,M);for(inti1;iN;i){if(i%M!0){printf(%d\n,i);}}return0;}这段代码的思路非常直接从 (1) 扫到 (N)只要i % M ! 0就说明 (i) 不是 (M) 的倍数把它输出并换行。核心判断只有一句if(i%M!0){printf(%d\n,i);}这道题属于“想清楚了就不会错”的类型判断条件单一输出格式简单边界情况也不复杂。唯一需要注意的是不要写反条件——i % M ! 0才是“不是倍数”写成i % M 0就变成只输出 (M) 的倍数了。当然最重要的是想到i % M ! 0,这一点很重要这是写这种题初学者的收获之一新手提醒遇到“跳过某类数”的题先想清楚保留的条件是什么再写ifi % M ! 0和i % M 0只差一个符号但结果完全相反提交前值得再看一眼读入两次scanf和合并成scanf(%d%d, N, M);都可以本题两种写法都能 AC如果题目对行末换行有额外要求再根据题面微调输出即可本题按行输出是标准做法。五、B4067 打印数字图案题的核心是“查表”不是“画图”题意数字 (0,1,2,3) 各有固定的 (5*5) 点阵表示。给定一个只由这些数字组成的串把每个数字按点阵逐行横向拼接后输出。参考写法#includestdio.h#includestring.hintmain(){chars[1002];constchar*pattern[4][5]{{.....,.***.,.***.,.***.,.....},{****.,****.,****.,****.,****.},{.....,****.,.....,.****,.....},{.....,****.,.....,****.,.....}};scanf(%s,s);intlenstrlen(s);for(introw0;row5;row){for(inti0;ilen;i){intds[i]-0;printf(%s,pattern[d][row]);}printf(\n);}return0;}这段代码的设计值得拆开看pattern[4][5]是一个“二维表”第一维是数字 (0 \sim 3)第二维是这个数字的 5 行图案。把“重复的视觉模式”抽象成“可索引的数据”是这道题最关键的思路转换。用const char *存每一行每个元素是指向字符串字面量的指针不需要手动管理\0也不用操心二维字符数组的长度。d s[i] - 0把字符数字转成下标这是字符题里非常常用的写法。外层循环row控制行号0 到 4内层循环i遍历数字串按pattern[d][row]查表输出正好实现“逐行横向拼接”。如果一开始试图用一堆if-else和循环变量去“画”图案代码很快会失控改一个数字的图案就要动全身。而“查表 按行拼接”的写法图案和逻辑是分离的以后哪怕题目再加数字 4、5、6也只需要往表里补几行。一个可以顺手简化的小点strlen(s)需要多遍历一次字符串。如果不想引入string.h也可以直接用s[i] ! \0作为内层循环条件for(introw0;row5;row){for(inti0;s[i]!\0;i){printf(%s,pattern[s[i]-0][row]);}printf(\n);}两种写法结果完全一样前者可读性更好后者少一次遍历、也少一个头文件。本题数据量很小两者都能 AC。新手提醒图案题优先考虑“查表”而不是“找规律硬拼”外层控制行号、内层遍历数字串是这类横向拼接题的通用框架字符数字转下标用s[i] - 0用const char *数组存字符串字面量比手写二维字符数组更省心也不会漏掉\0。一定要记录写这种题收获就比如我就收获了一点点知识——如果要将数字拆分成一位一位的可以先让其作为字符串字符串是一个特殊的数组于是我就可以通过特定的下标去快速、单独、且方便的形式去访问其次是字符串转整数s[i] - 0这个完全可以记下来以后大概会经常用得到非常方便。