☰
CFF CSP认证备考入门题目练习(复习c++语言适用)
2026/11/3 2:45:19 网站建设 项目流程

由于之前学习的是C,现在需要使用C++来备考CSP,所以先练习入门题目来熟悉C++的格式,考虑到本人编程能力较弱,所以从简单的题目入手更加能加强信心,帮助自己坚持下去,后续会持续更新博客,一方面可以巩固知识点,一方面可以记录学习过程监督自己,如果有不对的地方欢迎各位老师指正。

(补充一点,我用的洛谷刷题,下面记录了题序,可以去洛谷搜到)

一、顺序、输入输出、分支 if

第一题

P1001 A+B Problem|入门|【HelloWorld 级,熟悉输入输出】

题目描述

输入两个整数 a,b,输出它们的和。

任何一个伟大的思想,都有一个微不足道的开始。

输入格式

输入两个以空格分隔的整数 a,b。

输出格式

输出一个整数,表示 a+b。

代码展示

#include <iostream> using namespace std; int main(){ int a,b; cin >> a >> b; cout << a + b << endl; return 0; }

运行结果

知识点复习:

  • #include <iostream>
    C++的专属头文件,用来支持cin、cout(c++的输入输出).
  • using namespace std;
    cin和cout是放在std(标准命名空间)的.
  • cin >> a >> b;
    • cin = C++的输入,等价于C的scanf;
    • >> 叫做提取运算符,想象成箭头,从键盘流向变量.
  • cout << a+b << endl;

<<叫做插入运算符,箭头:把内容送到屏幕上打印;

endl意思:换行 + 刷新缓冲区,等价于\n.

第二题

P5710 数的性质|入门|判断奇偶、正负,练习 if 分支

题目描述

一些整数可能拥有以下的性质:

  • 性质 1:是偶数;
  • 性质 2:大于 4 且不大于 12。

小 A 喜欢这两个性质同时成立的整数;Uim 喜欢这至少符合其中一种性质的整数;小 B 喜欢刚好有符合其中一个性质的整数;正妹喜欢不符合这两个性质的整数。现在给出一个整数 x,请问他们是否喜欢这个整数?

输入格式

输入一个整数 x(0≤x≤1000)

输出格式

输出这 4 个人是否喜欢这个数字,如果喜欢则输出1,否则输出0,用空格分隔。输出顺序为:小 A、Uim、小 B、正妹。

代码展示

#include <iostream> using namespace std; int main(){ /数的性质/ int a; cin >> a; bool p1 = a%2 == 0,p2 = a>4 && a <=12; cout << (p1 && p2) << " " << (p1 || p2) << " " << (p1 != p2) << " " << (!p1 && !p2) <<endl; return 0; }

运行结果

知识点复习

cout << (p1 && p2);
  • 括号不能随便省略!<<优先级比&&、||高,不加括号会出错。
cout << x << " " << y;
  • 输出格式:多个数空格分隔

第三题

P5711 闰年判断|入门|经典多分支判断,CSP非常爱考这种判断逻辑

题目描述

输入一个年份,判断这一年是否是闰年,如果是输出 1,否则输出 0。

1582 年以来,闰年的定义:

普通闰年:公历年份是 4 的倍数,且不是 100 的倍数的,为闰年(如 2004 年、2020 年等就是闰年)。

世纪闰年:公历年份是整百数的,必须是 400 的倍数才是闰年(如 1900 年不是闰年,2000 年是闰年)。

输入格式

输入一个正整数 n,表示年份。

输出格式

输出一行。如果输入的年份是闰年则输出 1,否则输出 0。

代码展示

#include <iostream> using namespace std; int main(){ int a; cin >> a; if( (a % 4 == 0 && a % 100 != 0) || a % 400 ==0) { cout << 1 <<endl; } else{ cout << 0 <<endl; } return 0; }

代码运行

分析

此题根据看到题目的思路写出来和答案一致,并且没有出现比较模糊的知识点,所以只放出代码即可。

二、循环 for / while

考点:循环遍历、累加、计数,CSP-T1 90%都会用到循环

第一题

P1089 津津的储蓄计划|入门|按月循环,累加判断,经典模拟雏形

题目描述

津津的零花钱一直都是自己管理。每个月的月初妈妈给津津 300 元钱,津津会预算这个月的花销,并且总能做到实际花销和预算的相同。

为了让津津学习如何储蓄,妈妈提出,津津可以随时把整百的钱存在她那里,到了年末她会加上 20% 还给津津。因此津津制定了一个储蓄计划:每个月的月初,在得到妈妈给的零花钱后,如果她预计到这个月的月末手中还会有多于 100 元或恰好 100 元,她就会把整百的钱存在妈妈那里,剩余的钱留在自己手中。

例如 11月初津津手中还有 83 元,妈妈给了津津 300 元。津津预计11月的花销是 180 元,那么她就会在妈妈那里存 200 元,自己留下 183 元。到了 11 月月末,津津手中会剩下 3 元钱。

津津发现这个储蓄计划的主要风险是,存在妈妈那里的钱在年末之前不能取出。有可能在某个月的月初,津津手中的钱加上这个月妈妈给的钱,不够这个月的原定预算。如果出现这种情况,津津将不得不在这个月省吃俭用,压缩预算。

现在请你根据 2004 年 1 月到 12 月每个月津津的预算,判断会不会出现这种情况。如果不会,计算到 2004 年年末,妈妈将津津平常存的钱加上 20% 还给津津之后,津津手中会有多少钱。

输入格式

12 行数据,每行包含一个小于 350 的非负整数,分别表示 1 月到 12 月津津的预算。

输出格式

一个整数。如果储蓄计划实施过程中出现某个月钱不够用的情况,输出 −X,X 表示出现这种情况的第一个月;否则输出到 2004 年年末津津手中会有多少钱。

代码展示

#include <iostream> using namespace std; int main(){ /* 津津的储蓄计划 */ int money = 0; int tatal = 0; int rest = 0; int budget =0; int i ; for(i = 1; i &lt;= 12; i++){ cin &gt;&gt; budget; money = rest + 300; rest = money - budget; if((money - budget) &gt;= 0) /*第一次运行出现错误因为这里没有加上等于零的情况 */ { while(rest &gt;= 100) { tatal += 100; rest -= 100; } } else { cout &lt;&lt; -i; return 0; } } cout &lt;&lt; (tatal*1.2 + rest) &lt;&lt;endl; /*第二次编译错误因为这里没有考虑津津到月底还有剩余的钱,再加上妈妈给的钱 */ return 0; }

运行结果

分析

题目比较长,先在草稿纸上模拟一下整个流程,找出循环的点,再列出需要用到的变量,一一命名,写出框架后,输入数据编译,找出遗漏的细节。

第二题

P1035 级数求和|入门|while循环,控制循环终止条件

题目描述

已知:Sn​=1+1/2​+1/3​+…+1/n

。显然对于任意一个整数 k,当 n 足够大的时候,Sn​>k。

现给出一个整数 k,要求计算出一个最小的 n,使得 Sn​>k。

输入格式

一个正整数 k。

输出格式

一个正整数 n。

代码展示

#include <iostream> using namespace std; int main(){ /* 级数求和 */ int n = 0; int k = 0; double sn = 0; cin &gt;&gt; k; while(sn &lt;= k){ n ++; sn = sn + 1.0/n; } cout &lt;&lt; n; return 0; }

运行结果

分析

错误写法
int sn = 0; int n = 0; sn += 1/n; // 1/n 整数除法,全部是0

此处忽略了n为整型,1/n会忽略小数部分始终为0,所以在调试的时候sn始终为1,而n在不停增加;因此在定义和初始化变量以及做除法的时候需要注意变量的范围。

第三题

P1217 [USACO1.5]回文质数|普及-|循环枚举+判断,同时练循环+判断

题目描述

因为 151 既是一个质数又是一个回文数(从左到右和从右到左是看一样的),所以 151 是回文质数。

写一个程序来找出范围 [a,b](5≤a<b≤100,000,000)(一亿)间的所有回文质数。

输入格式

第一行输入两个正整数 a 和 b。

输出格式

输出一个回文质数的列表,一行一个。

复习

这道题需要我们回顾一下质数和回文数的判断方法。

我们先来看质数(也叫做素数),判断质数的方法可以在csdn上找到很多,我们今天只讲思维量最低的两种,比较好上手:

第一种是根据定义暴力遍历,时间复杂度为O(n),但是做题来说一般没问题。

质数的定义:大于一的自然数,除了一和本身,没有其他因数,就是质数。

所以我们只需要设立循环,把这个数依次除比它小的数,看有没有其他因数即可。

第二种方法和第一种方法类似,但是根据以下规则:

当一个数不是质数时,必定存在两个约数,一个大于等于 sqrt (n),另一个小于 sqrt (n)。

我们只需要遍历到sqrt (n)这个数即可,时间复杂度为O(sqrt (n)),降低了不少。

还有更多巧妙的方法我们目前做入门题目就先不做了解。

然后就是回文数(当然后面学到数组还会判断回文字符串,今天还是只用简单方法判断回文数吧)

首先从定义入手:从左到右和从右到左是看一样的数;

其次我们知道一个数如151除以十的余数是它的最后一个数1,除以十的商是前两个数15,由此我们可以单独得到每个位数上的数字,把个位数字×100,十位数字×10,百位数字×1,我们就得到倒过来的新数字,再比较新数和原数是否相等即可。

接下来开始写代码吧。

代码展示

#include <iostream> using namespace std; int main(){ /* 回文质数 / int a,b,i; bool flag = true; cin >> a >> b; for( a; a <= b; a++){ / 先用循环判断是不是质数 / for( i = 2; i < a; i++){ flag = true; if( a % i == 0){ / 只要不是质数就可以直接下一个数,不用再判断是不是回文数,所以命名一个标记,可以直接跳过回文数的判断 / flag = false; break; //不是质数跳出循环,开始遍历下一个数 } / 继续判断下一个数直至遍历完跳出循环 / } / 开始判断回文数 / if(flag) //是质数才进行判断 { int temp; int newed = 0; temp = a; while(temp > 0){ newed = newed10 + temp % 10; temp /= 10; } if(newed == a){ cout << a <<endl; continue; } } } return 0; }

先判断质数再判断回文数,且都用的暴力遍历,结果超时了,看了题解发现回文数比指数少,且可以先直接排除掉所有偶数,这样就可以省掉很多时间了。

#include <iostream> #include <cmath> using namespace std; int main(){ /* 回文质数 / int a,b,i; bool flag = true; cin >> a >> b; for( a; a <= b; a++){ flag = true; / 先排除偶数 / if(a % 2 == 0) { continue; } / 判断回文数 */ int temp; int newed = 0; temp = a; while(temp &gt; 0){ newed = newed*10 + temp % 10; temp /= 10; } if(newed != a){ continue; } /* 判断是不是质数 */ for( i = 2; i &lt;= sqrt(a); i++){ if( a % i == 0){ flag = false; //不是质数 break; } } if(flag){ cout &lt;&lt; a&lt;&lt; endl; } } return 0; }

直接遍历还是有部分超时,所以用了判断质数的第二种方法才通过了测评;

运行结果

复盘

  • 遍历的题一定要注意超时的问题;
  • 要用到数学库函数时,要加上对应头文件。
    #include <cmath>

三、数组(一维数组)

考点:数组读入、遍历、找最大/最小、统计、求和

第一题

P1047 校门外的树|入门|一维数组标记、区间修改,经典模拟

题目描述

某校大门外长度为 l 的马路上有一排树,每两棵相邻的树之间的间隔都是 1 米。我们可以把马路看成一个数轴,马路的一端在数轴 0 的位置,另一端在 l 的位置;数轴上的每个整数点,即 0,1,2,…,l,都种有一棵树。

由于马路上有一些区域要用来建地铁。这些区域用它们在数轴上的起始点和终止点表示。已知任一区域的起始点和终止点的坐标都是整数,区域之间可能有重合的部分。现在要把这些区域中的树(包括区域端点处的两棵树)移走。你的任务是计算将这些树都移走后,马路上还有多少棵树。

输入格式

第一行有两个整数,分别表示马路的长度 l 和区域的数目 m。

接下来 m 行,每行两个整数 u,v,表示一个区域的起始点和终止点的坐标。

输出格式

输出一行一个整数,表示将这些树都移走后,马路上剩余的树木数量。

复习

数组的内容有点忘了,复习一下再来做题。

定义方式:
数据类型 数组名 [数组长度];
数据类型 数组名 [数组长度] = {值1 ,值2,...};
数据类型 数组名 [] = {值1 ,值2,...};
数组名的用途:
sizeof(arr)可以统计整个数组在内存中的长度;
sizeof(arr[0])某个数在内存中的长度;
sizeof(arr)/ sizeof(arr[0]) = 数组个数;
cout << arr << endl; 获取数组在内存中的首地址;

一些简单的知识点,这道题可能用不上,但是无所谓,开始做题吧。

思路分析

这道题的思路可以拆成三步来想。

第一步:确定数组大小。马路长度为 l,数轴上 0 到 l 每个整数点都有一棵树,所以一共有 l+1 棵树。因此数组长度要开成 l+1,下标 0 到 l 正好一一对应每一棵树的位置。

第二步:想清楚“移走”怎么表示。静态数组没办法真的把元素删掉,而且题目给的区间之间还可能有重合部分,如果直接对数组元素做删除或置空,重合区间就会重复处理,很容易出错。所以换一种思路:不记录“树还在不在”,而是记录“这棵树有没有被标记过”。用 0 表示还没被移走,用 1 表示已经被移走。

第三步:标记与统计。先把整个数组初始化为 0,表示所有树都还在。然后每读入一组区间 u、v,就把下标 u 到 v 之间的元素全部置为 1,表示这些位置上的树被移走了。由于是直接赋值 1,即使区间重合,重复标记也不会影响结果。最后再遍历一遍数组,统计值为 0 的元素个数,就是马路上剩余的树木数量。

这样写的好处是逻辑非常直观:标记阶段只负责“打点”,统计阶段只负责“数数”,两个阶段互不干扰,也不需要考虑区间重合带来的复杂情况。

代码展示

#include <iostream> using namespace std; int main(){ /* 校门外的树 */ int l,m,u,v; int num = 0; cin >> l >> m; int v1[l+1]; for( int i = 0; i < l + 1; i ++){ v1[i] = 0; } for( int i = 0; i &lt; m; i ++){ cin &gt;&gt; u &gt;&gt; v; for( u; u &lt;= v; u++){ v1[u] = 1; } } for( int i = 0; i &lt; l + 1; i ++){ if( v1[i] == 0 ) { num ++; } } cout &lt;&lt; num; return 0; }

看了题解后意识到,数组里面只有1/0,变量类型其实可以定义为bool型,这样bool的初始值就为false,就可以少一个遍历定义数组的过程。

运行结果

第二题

P1059 明明的随机数|入门|去重+排序,数组基础综合题

题目描述

明明想在学校中请一些同学一起做一项问卷调查,为了实验的客观性,他先用计算机生成了 N 个 1 到 1000 之间的随机整数 (N≤100),对于其中重复的数字,只保留一个,把其余相同的数去掉,不同的数对应着不同的学生的学号。然后再把这些数从小到大排序,按照排好的顺序去找同学做调查。请你协助明明完成“去重”与“排序”的工作。

输入格式

输入有两行,第 1 行为 1 个正整数,表示所生成的随机数的个数 N。

第 2 行有 N 个用空格隔开的正整数,为所产生的随机数。

输出格式

输出也是两行,第 1 行为 1 个正整数 M,表示不相同的随机数的个数。

第 2 行为 M 个用空格隔开的正整数,为从小到大排好序的不相同的随机数。

复习

这道题涉及到“去重”和“排序”两个点,去重用遍历比较就可以解决,排序却是个比较经典,解法也很丰富的算法题,我们先来复习几种简单常见的排序算法吧。

关于排序是参考以下作者的做法和思路

原文链接:https://blog.csdn.net/2301_80215560/article/details/136604635

一、冒泡排序

通过对待排序序列从前向后(从下标较小的元素开始),依次对相邻两个元素的值进行两两比较,若发现前一个数大于后一个数则交换,使值较大的元素逐渐从前移向后部,就如果水底下的气泡一样逐渐向上冒。

#include <iostream> using namespace std; /* 冒泡排序 */ int swap( int *a, int *b) { int temp = *a; *a = *b; *b = temp; }//因为冒泡排序是比较相邻的两个数,并按照排序规则交换顺序,所以会使用交换这个动作很多次 int bubble(int *arr, int n){ for( int i = 0; i &lt; n; i++){ int change = 0; for( int j = 0; j &lt; n-i-1; j++){ //由于每一轮最大的数已经确定,所以每次可以少比较一个 if( arr[j] &gt; arr[j+1] ){ swap( arr[j], arr[j+1]); change ++;//如果在一整轮循环中没有交换,则说明已经排好了,所以有交换我们就标记一下 } } if(change == 0) { break; //一轮循环没有交换,直接跳出循环,减少时间 } } } int main(){ int arr[5] = {3,4,7,2,0}; bubble( arr, 5); for( int i = 0; i < 5; i++){ cout << arr[i]<<' '; } }
二、选择排序

内层循环一趟找出最小值的下标,与第一个数交换。重复找小,交换的两个操作。

#include <iostream> #include <utility> using namespace std; /* 选择排序 */ int SelectSort( int *arr, int n) { for( int i = 0; i < n - 1; i++){ int min = i;//一开始把这一步定义min写在了下面一层循环里面 怎么运行都不对 因为写在下面那个for循环就是局部变量 出了这个循环 下面就不能再引用 for( int j = i; j < n; j++){ if(arr[j] < arr[min]){ min = j; } } swap(arr[i], arr[min]); //swap函数在头文件<utility>里面 可以直接调用 } } int main(){ int arr[5] = {3,4,7,2,0}; SelectSort( arr, 5); for( int i = 0; i < 5; i++){ cout << arr[i]<<' '; } }
三、插入排序

在待排序的元素中,假设前n-1个元素已有序,现将第n个元素插入到前面已经排好的序列中,使得前n个元素有序。按照此法对所有元素进行插入,直到整个序列有序。
但我们并不能确定待排元素中究竟哪一部分是有序的,所以我们一开始只能认为第一个元素是有序的,依次将其后面的元素插入到这个有序序列中来,直到整个序列有序为止。

#include <iostream> #include <utility> using namespace std; /* 插入排序 */ int InsertSort( int *arr, int n) { for(int i = 1; i < n; i++){ //从下标为1开始 if(arr[i] < arr[i-1]) {//先判断 如果要插入的这个数比前面有序数组的最后一个大就直接放到最后一个就好了 即不用变化 //现在我们要开始挨个和前面的数比较 找到合适的位置 //当前面一个数比当前数大时 把前面一个数往后移一个 //此时当前这个arr[i]的位置变成了更大的这个数 那么我们就要先定义一个临时变量把我们拿出来要插入的数存起来 int temp; temp = arr[i]; int j; //把要插入的数挨个往前比较 直到到了第一个数或者找到了比它小的 即下标等于0了或者arr[j]<arr[i] for( j = i-1; j >= 0 && temp < arr[j]; j --){ //还没有检索到合适的位置,就把当前的arr[j]往后移一个 arr[j + 1] = arr[j]; } //直到检索到的这个值arr[j]比temp小 把temp赋值给j后面这个位置 //或者已经把下标为0的数往后移另一位 所以j--后为-1 不会再进入循环 把要插入的值放入j+1也就是0下标 arr[j + 1] = temp; } } } int main(){ int arr[5] = {3,4,7,2,0}; InsertSort( arr, 5); for( int i = 0; i < 5; i++){ cout << arr[i]<<' '; } }

排序方法太多了,今天就学到这里好了,光这三种方法就弄了几个小时,还是继续刷题复习基础吧。

代码展示

#include <iostream> #include <vector> using namespace std; void InsertSort( int *arr, int n) { for(int i = 1; i < n; i++){ //从下标为1开始 if(arr[i] < arr[i-1]) {//先判断 如果要插入的这个数比前面有序数组的最后一个大就直接放到最后一个就好了 即不用变化 //现在我们要开始挨个和前面的数比较 找到合适的位置 //当前面一个数比当前数大时 把前面一个数往后移一个 //此时当前这个arr[i]的位置变成了更大的这个数 那么我们就要先定义一个临时变量把我们拿出来要插入的数存起来 int temp = arr[i]; int j; //把要插入的数挨个往前比较 直到到了第一个数或者找到了比它小的 即下标等于0了或者arr[j]<arr[i] for( j = i-1; j >= 0 && temp < arr[j]; j --){ //还没有检索到合适的位置,就把当前的arr[j]往后移一个 arr[j + 1] = arr[j]; } //直到检索到的这个值arr[j]比temp小 把temp赋值给j后面这个位置 //或者已经把下标为0的数往后移另一位 所以j--后为-1 不会再进入循环 把要插入的值放入j+1也就是0下标 arr[j + 1] = temp; } } } int main(){ //先排序再去重 这里排序我们直接调用我们前面写过的函数 int N; cin >> N; vector<int> arr(N); for( int i = 0; i < N; i++) { cin >> arr[i]; } InsertSort( arr.data(), N); //去重 把不重复的数记录下来 int k = 0;//用k来记录不重复数 for( int i = 0; i < N; i++) { //第一个数直接记录下来 再比较 和后一个数不同的也记录下来 if(k == 0 || arr[k-1] != arr[i]) { arr[k++] = arr[i]; } } cout << k <<endl; for( int i = 0; i < k; i++){ cout << arr[i]<<' '; } return 0; }

运行结果

总结

这里把做错的地方列举一下:

问题 1:cout << arr[i]<<'';— 编译错误C++
cout << arr[i]<<'';

''是空字符字面量,C++ 不允许空字符(字符必须包含至少一个字符)。编译器会直接报错。

cout << arr[i] << ' '; // 用空格字符 ' ' // 或者 cout << arr[i] << " "; // 用字符串 " "

没有 return — 未定义行为

int InsertSort( int *arr, int n) { // 返回类型是 int for(int i = 1; ... ) { ... } // ← 没有 return 语句! }

修复:函数不需要返回值,改为void:

void InsertSort( int *arr, int n) {
问题 3:使用了 VLA(变长数组)— 标准 C++ 不支持
int arr[N]; // N 是运行时变量

这是 C99 的 VLA 特性,标准 C++(C++11/14/17/20)不支持。GCC 作为扩展可以编译,但 Clang/MSVC 可能不行。

修复:

#include <vector> // ... vector<int> arr(N);

第三题

P1781 宇宙总统|普及-|读入一堆数据,找最大值下标,结构体雏形

题目描述

地球历公元 6036 年,全宇宙准备竞选一个最贤能的人当总统,共有 n 个非凡拔尖的人竞选总统,现在票数已经统计完毕,请你算出谁能够当上总统。

输入格式

第一行为一个整数 n,代表竞选总统的人数。

接下来有 n 行,分别为第一个候选人到第 n 个候选人的票数。

输出格式

共两行,第一行是一个整数 m,为当上总统的人的号数。

第二行是当上总统的人的票数。

代码展示

#include <iostream> using namespace std; int main(int argc, char** argv) { /*宇宙总统*/ int n; //题目中说明票数可能会很大 可能会到 100 位数字 所以不能用int(int只能存大约10位数) string s; string max = "";//设一个变量来存储最大值 int maxNum = 1;//用来存最大值的下标 cin >> n; for(int i = 0; i < n; i++){ cin >> s; //这里要用字符串比较大小的方法 if(s.size() > max.size() || (s.size() == max.size() && s > max)) { max = s; maxNum = i + 1; } } cout << maxNum <<endl; cout << max <<endl; return 0; }

运行结果

总结

①问题:int存不下 100 位的数
类型最大值大约位数
int214748364710 位
long long约 9.2×10¹⁸19 位
题目票数可达100 位

string存的不是"一个数",而是"一串字符"。它根本没有"数值范围"这个概念,只要内存够,想存多长存多长。

②string的用法

s.size():得到字符串的长度

③用string比较大小

字符串从左往右比,只看最高位。"9"的最高位是 9,"100"的最高位是 1,于是"9" > "100"——完全忽略了"100"后面还有两位。

string的直接比大小只适合在位数相同时当数值比较用;位数不同时它会误判("9" > "100")。所以必须先用size()比较位数得出谁更大,长度相同才落到字典序比较上。


好了,这一篇就到这里了,基础入门题目还有五节就放下一篇了,这一篇已经做了三天,先完结一下。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询