洛谷C语言学习小记

发布时间:2026/7/19 2:14:28
洛谷C语言学习小记 近暑假闲的没事做想要温习一下C语言从ACM大佬那里得知了一个刷题网站洛谷。搜到一个博客是关于洛谷题解的洛谷编程题解指南-CSDN博客因此本着学习的心态记录一下自己觉得有意思的题目因为是初学者这里就光做入门的题目了待到编程能力提高了一点再做做拔高的。三连击P1008这道题我刚开始做错的地方是没读懂题干将12…9共9个数分成三组分别组成三个三位数且使这三个三位数构成123的比例试求出所有满足条件的三个三位数。首先我看到了比例思路是用a代表那一份然后用2*a和3*a分别代表另外两个数字自然想到了只需要表示a即可。刚开始我还嵌套循环表示a其实没那么复杂一个从123开始的for循环就可以搞定然后就是划定一下b和c的范围不可以超过999.现在只剩下最后一个难关怎么确保这些数字没有重复而且是1-9组成的问了kimi只需要初始化一个count[10]数组即可。写一个循环配合这个数组用来计数怎么提取每一位的数字呢可以只看最后一位然后不断左移就是%10和/10的循环罢了。然后就是需要保证没有0的存在没有重复的数字具体算法可以参考以下代码#includeiostream #includestdio.h #pragma warning(disable:6385)//抑制数组越界警告 using namespace std; int main() { for (int a 123; a 987; a) { int b a * 2; int c a * 3; if (b 999 || c 999) continue; int cnt[10] { 0 }; int nums[3] { a, b, c }; for (int i 0; i 3; i) { int tmp nums[i]; while (tmp 0) { cnt[tmp % 10]; tmp / 10; } } //计数部分 bool ok true; if (cnt[0] 1) ok false; for (int i 1; i 10; i) { if (cnt[i] ! 1) ok false; } if (ok) { cout a b c endl; } } return 0; }因为我这里编译器提示越界实则并没有越界加了一个警告提示符这里说明下洛谷P1980题目思路和这个题目有相同之处所以这个计数方法可以留意一下。小鱼的航程P1424题目描述就不写了就是说这只fish每周六日都会休息其他时间都是每天前进250米。这道题之所以记录是因为我觉得我的思路很不错因为星期几都是固定的取余可以很好的完成这个任务所以一个if就可以轻松判断是否是工作日#includeiostream #includestdio.h using namespace std; int main() { int x ; long n; cin x n; int miles 0; for (int i 0; i n; i) { if (!(x % 7 6 || x%70)) { miles 250; } x; } cout miles; return 0; }校门外的树洛谷-P1047题目描述某校大门外长度为L的马路上有一排树每两棵相邻的树之间的间隔都是1米。我们可以把马路看成一个数轴马路的一端在数轴0的位置另一端在L的位置数轴上的每个整数点即012……L都种有一棵树。由于马路上有一些区域要用来建地铁。这些区域用它们在数轴上的起始点和终止点表示。已知任一区域的起始点和终止点的坐标都是整数区域之间可能有重合的部分。现在要把这些区域中的树包括区域端点处的两棵树移走。你的任务是计算将这些树都移走后马路上还有多少棵树。通过这个题目了解到一个新知识当提示这个信息的时候是因为C/C 里写在函数内部直接定义的数组 / 大变量是放在栈stack里的Windows VS 默认程序栈大小一般只有1MB约 1024×10241048576 字节虽然 10000 个 int (40KB) 没到上限但 VS 静态检查会提前告警局部数组太大不推荐放栈上所以我们把这个写在main函数外部就不会有问题了某校大门外长度为l的马路上有一排树每两棵相邻的树之间的间隔都是 1 米。我们可以把马路看成一个数轴马路的一端在数轴 0 的位置另一端在l的位置数轴上的每个整数点即 0,1,2,…,l都种有一棵树。由于马路上有一些区域要用来建地铁。这些区域用它们在数轴上的起始点和终止点表示。已知任一区域的起始点和终止点的坐标都是整数区域之间可能有重合的部分。现在要把这些区域中的树包括区域端点处的两棵树移走。你的任务是计算将这些树都移走后马路上还有多少棵树。根据题目描述长度为L因为起点是0所以数组长度没有弄好长度是L的话应该是一共有L1颗树的下面是我的代码#includeiostream #includestdio.h using namespace std; int trees[10000]; int main() { int l, m,a,b; cin l m; int r0; for (int i 0; i l; i) { trees[i] 1; } for (int i 0; i m; i) { cin a b; for (int j a; j b; j) { trees[j] 0; } } for (int i 0; i l; i) { if (trees[i] 0) { r; } } cout l1-r; return 0; }统计单词数洛谷-P1308我这里因为暂时不会KMP算法因此就采取暴力一点的方式统计单词数量了题目描述给定一个单词请你输出它在给定的文章中出现的次数和第一次出现的位置。注意匹配单词时不区分大小写但要求完全匹配即给定单词必须与文章中的某一独立单词在不区分大小写的情况下完全相同参见样例1 如果给定单词仅是文章中某一单词的一部分则不算匹配参见样例2 。第一思路就是首先得利用空格分隔出来单词形成单词列表后通过数组的形式进行匹配。第二思路是因为单词必须得保证是大小写忽略因此我们先转换单词为全是小写吧这一步可以在单词数组初始化的时候进行。这里需要格外注意C语言字符串的结尾是\x00所以第二步的时候不要忘记加上这是我的刚开始的想法后来对答案的时候发现和标准答案还是有很大的差别的。而且对for循环我的理解肯定比不过标准答案。具体来说就是for循环初始化那一部分可以写上希望在循环开始的时候执行一次的代码相应的循环变量处理的时候可以写每次循环结束后希望执行的代码。按照我的思路来说还是比较麻烦还得单独写一个变量存储单词首字母的出现位置这个时机我觉得在单词转换的时候就不错转换单词的时候直接写入首字母的位置到一个和单词数组配套的数组即可#include iostream #include cstring using namespace std; char input[10000005]; char a[1005]; char c[500005][105]; int start_pos[500005]; int main() { cin a; cin.ignore(100000, \n);//清缓冲区 cin.getline(input, sizeof(input));//防止空格后的内容丢失 int len strlen(input); int a_len strlen(a); for (int i 0; i a_len; i) a[i] tolower(a[i]); int word_count 0; int pos 0;//单词内部指针 int word_start 0; for (int i 0; i len; i) { if (input[i] || input[i] \0) { //说明到了一个单词的结尾 if (pos 0) { c[word_count][pos] \0; start_pos[word_count] word_start; word_count; pos 0; } } else { //是单词开头 if (pos 0) word_start i; c[word_count][pos] tolower(input[i]); } } int index -1, flags 0; for (int i 0; i word_count; i) { if (strcmp(c[i], a) 0) { if (index -1) index start_pos[i]; flags; } } if (flags) cout flags index; else cout -1; return 0; }按照大佬的思路比较的时候无非是在单词刚开始的时候进行比较所以我们也没必要非要转换为数组反而浪费时间。不如直接抓住这时候前一位是一个空格然后进行逐个比较即可。至于首字母的时机在比较前直接赋值就行了正如刚才说的for循环理解就是应用到了这个地方附上大佬代码#includeiostream #includecstring #includecstdio using namespace std; int main() { char confirm[11],article[1000001]; int i,j,total0,flag0,work-1; gets(confirm); gets(article); for(i0;confirm[i]!\0;i)//全部转化为小写字母 if(confirm[i]Aconfirm[i]Z) confirm[i]char(confirm[i]32); for(i0;article[i]!\0;i)//全部转化为小写字母 if(article[i]Aarticle[i]Z) article[i]char(article[i]32); for(i0;article[i]!\0;i)//统计过程 { if((i0||article[i-1] )(confirm[0]article[i]))//划分单词 { for(flagi,j0;confirm[j]!\0;j,i)//但凡有一个字母不同终止本轮循环 if(confirm[j]!article[i]) break; if(strlen(confirm)j(article[i] ||article[i]\0))//有相同单词进行统计 { total; if(total1) workflag; } } } if(total0) couttotal workendl;//存在相同单词输出个数 else coutworkendl;//不存在相同单词输出-1 return 0; }小书童——密码洛谷-P1914实际上这道题就是实现了一次凯撒加密。凯撒加密移位算法在数学里面能用mod 26表示这也是一样因此还算稍微简单的算法#includeiostream #include cstring using namespace std; int main() { char password[1000000]; int offset; cin offset; cin password; for (int i 0; i strlen(password); i) { password[i] 97 (password[i] offset - 97) % 26; } cout password; return 0; }这道题为社么想要记录是因为和大佬思路不一样我是想直接寻找到偏移量再加上’a’大佬思路是想一步到位通过原来的字母直接加上对应的偏移得到新字母所以按照后者的做法需要单独写一个判断来校验是否超过了’z’。我感觉我的这个思路还算是比较简洁。做逆向题的时候经常见数字反转升级版洛谷-P1553这道题真的没有一点头绪询问了chatGPT。他给了一个需要用到一些库函数的做法思路是由于小数这些特殊数字的后面反转之后去除后置0而整数反转后去除前导0那我们不如写一个if判断只要将数字区分开就好办了如果杂糅在一起不是太好处理。#include iostream // 输入输出 #include vector // 动态数组 #include algorithm // sort排序 using namespace std; using namespace std; string rev(string s) { reverse(s.begin(), s.end()); int i 0; while (s[i] 0 i s.size() - 1) { i; } return s.substr(i); } int main() { string s; cin s; if (s.back() % ) { string a s.substr(0,s.size() - 1); cout rev(a) %; } //分数 else if (s.find(/) ! string::npos) { int p s.find(/); string a s.substr(0, p); string b s.substr(p 1); cout rev(a) / rev(b); } //小数 else if (s.find(.) ! string::npos) { int p s.find(.); string a s.substr(0, p); string b s.substr(p 1); a rev(a); reverse(b.begin(), b.end()); //处理小数末尾0 bool allzero true; for (char c : b) { if (c ! 0) { allzero false; break; } } if (allzero) { b 0; } else { while (b.back() 0) b.pop_back(); } cout a . b; } //整数 else { cout rev(s); } return 0; }大佬的做法又是让我想不到的一次。上面的做法很显然作弊了用了太多的库函数这个主要没想到的地方是如何分类大佬做法简单粗暴就是单独搞一个flag当作符号位到时候根据符号位前还是后反转即可。但是不想做就是因为自己没有在玩下一步想特殊点就是多了一个符号根据符号在哪里进行进一步思考就可以。由此引发了我进一步的思考高中的时候还在想如何将一道题写出来就是检查题考的知识如何将题目映射到知识上有的题都是根据某一类题目演化做的多了就会了。因此当时还尝试过写母题。C语言\CTF不都是如此都有基础知识的要求都有一类题目特别经典。对于经典的题目应该思考思考他怎么结合、结合了哪些知识点然后熟能生巧对于这种反转的题无非就是倒着写一个for循环。复杂的地方在于需要写几个条件分支而已。#includeiostream #includecstring using namespace std; int main() { char num[100000] {0}; char result[100000] {0}; cin num; int len strlen(num); int index len; char flag 0; int i; // 找特殊符号位置 for(i 0; i len; i) { if(num[i]. || num[i]/ || num[i]%) { flag num[i]; index i; break; } } // // 整数 // if(index len) { // 整体反转 for(i0;ilen;i) { result[i]num[len-i-1]; } // 去前导0 int start0; while(startlen-1 result[start]0) { start; } for(istart;ilen;i) { coutresult[i]; } } // // 小数 // else if(flag.) { // 整数部分反转 for(i0;iindex;i) { result[i]num[index-i-1]; } // 小数点 result[index].; // 小数部分反转 for(iindex1;ilen;i) { result[i]num[len-iindex]; } // 去整数部分前导0 int start0; while(startindex-1 result[start]0) { start; } // 去小数部分末尾0 int endlen-1; while(endindex1 result[end]0) { end--; } result[end1]\0; // 输出 for(istart;result[i]!\0;i) { coutresult[i]; } } // // 分数 // else if(flag/) { // 分子反转 for(i0;iindex;i) { result[i]num[index-i-1]; } result[index]/; // 分母反转 for(iindex1;ilen;i) { result[i]num[len-iindex]; } result[len]\0; // 分子去前导0 int start0; while(startindex-1 result[start]0) { start; } // 输出分子 for(istart;iindex;i) { coutresult[i]; } // 分母去前导0 startindex1; while(startlen-1 result[start]0) { start; } // 输出分母 for(istart;ilen;i) { coutresult[i]; } } // // 百分数 // else if(flag%) { // 数字部分反转 for(i0;iindex;i) { result[i]num[index-i-1]; } // 去数字部分前导0 int start0; while(startindex-1 result[start]0) { start; } // 输出数字 for(istart;iindex;i) { coutresult[i]; } // 输出百分号 cout%; } return 0; }用一句毛主席的诗说就是自信人生二百年会当水击三千里。回文质数(P1217)这里打算用递归实现这个算法听说递归很耗时间这里只是作为锻炼编程能力。原代码是通过循环构造回文数并且判断是否都是质数来实现的#include iostream using namespace std; // 辅助函数获取数字位数 int getLen(int n) { if (n 10) return 1; return 1 getLen(n / 10); } // 递归判断回文数 bool isPalindrome(int num) { if (num 0) return false; if (num 10) return true; int len getLen(num); int high num / (int)pow(10, len - 1); int low num % 10; if (high ! low) return false; int next (num % (int)pow(10, len - 1)) / 10; return isPalindrome(next); } int main() { int x; cin x; if (isPalindrome(x)) cout 是回文数; else cout 不是回文数; return 0; }我这个代码没有考虑是素数的情况数的计算P2001这个题直接让我失去了思路根本找不到相关的表达式了最后看了ChatGPT的回答了解到估计是找到一个递推关系假设f(n)表示以n开头的数列构造后满足条件的个数比如f(6)第二个数字可以是3【63】本身是一个数列排除这种情况那么f(6)可以拆成1f(3)。6后面同样可以跟123.因此这就是一个递归函数f(6)到f(3)就是n/2的改变而已据此写出递归函数只要用递归就会提示[TLE]也就是时间超出限制。所以我们需要加上递归的记忆啥意思呢只需要加一个数组减少大量重复计算即可就像下面这样。#include iostream using namespace std; long long dp[100005]; long long solve(int i) { if(dp[i]) return dp[i]; long long a1; for(int j1;ji/2;j) { asolve(j); } dp[i]a; return a; } int main() { int n; cinn; coutsolve(n); return 0; }问了大佬说这种前一步操作影响下一步的题就是动态规划或递归的思路。火柴棒等式洛谷-P1149这道题真的是没有一点点思路问了chatGPT。我们其实抓住关键一点已知条件是火柴棒的每个数字的条数分析一个等式成立的条件肯定是火柴棒数量能对上。我们编程里面需要一个条件这就是一个筛选条件然后枚举即可。数字分别需要这些根火柴棒number[10]{6,2,5,5,4,5,6,3,7,6}我们还需要4根拼接成和符号。据此写出脚本筛选即可。而且题目这句话可以视为一个简单的提示如果A\B则ABC与BAC视为不同的等式A,B,C≥0就是让我们大胆枚举#include iostream using namespace std; int sum(int i) { int r; int number[10] { 6,2,5,5,4,5,6,3,7,6 }; if (i / 10 0) { r number[i % 10] sum(i / 10); } else { r number[i % 10]; } return r; } int main() { int n, r0; cin n; for (int i 0; i 999; i) { for (int j 0; j 999; j) { if (sum(i) sum(j) sum(i j)4 n) { r; } } } cout r; }数字反转洛谷-P1307这里主要记载另一种数字反转的思路比如一个三位数abc反转的时候本质上就是数字的位权发生了变化。比如反转之后c成为了原来的100倍。怎么用代码实现呢从0开始每一次乘以10实现位权增大让c最开始乘10b次之a不乘即实现位权的变化。这样说大家可能难以理解配合下面实例很好理解while(n!0) { sumsum*10(n%10); n/10; }P2089 烤鸡刚开始没想到枚举的思路小看电脑算力了看有人用搜索算法我还没学到只能说是留着以后填坑巴#includeiostream using namespace std; int main() { int n, sum 0; int ing1, ing2, ing3, ing4, ing5, ing6, ing7, ing8, ing9, ing10; cin n; /*每种材料依次遍历当相加符合美味度时方案数1*/ for (ing1 1; ing1 3; ing1) for (ing2 1; ing2 3; ing2) for (ing3 1; ing3 3; ing3) for (ing4 1; ing4 3; ing4) for (ing5 1; ing5 3; ing5) for (ing6 1; ing6 3; ing6) for (ing7 1; ing7 3; ing7) for (ing8 1; ing8 3; ing8) for (ing9 1; ing9 3; ing9) for (ing10 1; ing10 3; ing10) if (ing1 ing2 ing3 ing4 ing5 ing6 ing7 ing8 ing9 ing10 n) sum; cout sum endl;//输出方案数 /*每种材料依次遍历当相加符合美味度时输出当前方案*/ for (ing1 1; ing1 3; ing1) for (ing2 1; ing2 3; ing2) for (ing3 1; ing3 3; ing3) for (ing4 1; ing4 3; ing4) for (ing5 1; ing5 3; ing5) for (ing6 1; ing6 3; ing6) for (ing7 1; ing7 3; ing7) for (ing8 1; ing8 3; ing8) for (ing9 1; ing9 3; ing9) for (ing10 1; ing10 3; ing10) { if (ing1 ing2 ing3 ing4 ing5 ing6 ing7 ing8 ing9 ing10 n) { cout ing1 ; cout ing2 ; cout ing3 ; cout ing4 ; cout ing5 ; cout ing6 ; cout ing7 ; cout ing8 ; cout ing9 ; cout ing10 ; cout endl; } } return 0; }计算器的改良洛谷-P1022通过这道题认识一种编程思想人脑是可以模拟很多情况的就跟这道题一样是需要编写程序算方程。但是电脑又不会他分不清现在处于那种情况因此我们需要维护几个状态变量。原来我的想法是写的大量if但是这样自己就绕了进去。实际的做法应该是保存几个状态变量即可。这也跟计组里面的计算机状态有异曲同工之妙。以后写代码时如果发现if(...) else if(...) else if(...) else if(...)越来越长而且每个条件都在考虑“之前发生了什么”。通常说明缺少一个状态变量。应该把之前发生了什么保存下来。这是我废了老大劲写的第一版没有AC#includeiostream #includectype.h #includecstring using namespace std; int main() { int num 0, alpha 0, c0; int work 1;//判断是等号左边还是右边 double r; char x; int flag 1; char a[100]; cin a; for (int i 0; i strlen(a); i) { if (isdigit(a[i])) { num num * 10 (a[i] - 0); } else if (isalpha(a[i])) { x a[i]; if (num 0) { num 1; } alpha work * flag * num; num 0; flag 1; } else if (a[i] ) { c flag * work * num; work -1; flag 1; num 0; } else { c work * flag * num; num 0; flag 1; if (a[i] -) { flag -1; } } } c num * flag * work; r -c / (double)alpha; cout x ; printf(%.3f, r); return 0; }主要在第五个测试点出问题了“最后经过很多测试数据测试发现问题在这里0x1x0 x-0.500我代码没有考虑x前面系数本身为0.因此需要新的状态变量存储字母前面有没有其他的数字。而且需要注意-0和0评判的系统会不认。所以需要特殊处理#includeiostream #includectype.h #includecstring using namespace std; int main() { int num 0, alpha 0, c 0; bool hasNum false; int work 1;//判断是等号左边还是右边 double r; char x; int flag 1; char a[100]; cin a; for (int i 0; i strlen(a); i) { if (isdigit(a[i])) { num num * 10 (a[i] - 0); hasNum true; } else if (isalpha(a[i])) { x a[i]; if (hasNum) { alpha work * flag * num; } else { alpha work * flag; } num 0; flag 1; hasNum false; } else if (a[i] ) { c flag * work * num; work -1; flag 1; num 0; hasNum false; } else { c work * flag * num; num 0; hasNum false; flag 1; if (a[i] -) { flag -1; } } } c num * flag * work; r -c / (double)alpha; if (!r) { cout x ; printf(0.000); } else { cout x ; printf(%.3f, r); } return 0; }P1036 [NOIP 2002 普及组] 选数看大佬的博客是用到了深度优先算法这里不太会这个算法想想有没有别的思路但是如果用我的这个思路素数选择就必须用数论知识进行缩短用埃式筛这样才能实现不TLE具体思路是每个数字只有俩状态选到或者没选到。因此可以用二进制来表示每个数字比如1234随机选1个是不是可以刚好用0001001001001000来表示这里让我搞不清的就是怎么确定1的数量。后来还是想到了状态机的思路。那就是只需要有1的时候让计数器变量1即可。有思路了先实现选数字素数的筛选一会再说。int main() { int n, k,r0; int a[20]; cin n; cin k; int cnk0,sum0; init(); for (int i 0; i n; i) { cin a[i]; } for (int i 0; i (1 n); i) { sum 0; cnk 0; for (int j 0; j n; j) { if ((i (1 j)) 0) { cnk; sum a[j]; } } if (cnk kprime[sum]) { r; } } cout r; return 0; }再来看看素数筛选要找出2~n 之间所有质数先默认全部数字都是质数从最小质数2开始把它所有倍数全部标记为非质数找到下一个没被标记的数它就是新质数再标记它所有倍数循环直到遍历到 根号n剩余未标记的数全部是质数。bool prime[100000005]; void init() { //先全部认为是质数 for (int i 2; i 100000000; i) prime[i] true; //筛质数 for (int i 2; i * i 100000000; i) { if (prime[i]) { for (int j i * i; j 100000000; j i) { prime[j] false; } } } }这里需要注意int prime[100000005];占用空间小int prime[100000005] { 1 };占用空间很大。后者可能会导致编译错误为了进一步缩减空间int也应该换成bool表示。