2023 第一轮真题解析(二):阅读程序题

2023 第一轮真题解析(二):阅读程序题

2023 年 CCF 非专业级软件能力认证(CSP-J/S 2023)第一轮认证于 2023 年 9 月 16 日举行。继上一篇单项选择题解析后,本文为您带来 第二部分:阅读程序题(共 3 大题,计 40 分) 的逐题源码分析、算法推演与深度全解析。

阅读程序题主要考查对 C++ 格式化输出、海伦公式、动态规划最长公共子序列(LCS)、字符串匹配与移位、试除法分解约数及数论性质 等知识点的掌握。


📌 二、阅读程序(程序输入不超过数组或字符串定义的范围;判断题正确填 √,错误填 ×;除特殊说明外,判断题 1.5 分,选择题 3 分,共计 40 分)


📍 第一题:海伦公式计算三角形面积与格式化输出

💻 源码展示

#include<iostream>
#include<cmath>
using namespace std;

double f(double a,double b,double c){
    double s=(a+b+c)/2;
    return sqrt(s*(s-a)*(s-b)*(s-c));
}
int main(){
    cout.flags(ios::fixed);
    cout.precision(4);

    int a,b,c;
    cin>>a>>b>>c;
    cout<<f(a,b,c)<<endl;
    return 0;
}

假设输入的所有数都是不超过 1000 的正整数,完成下面的判断题和单选题:

💡 程序主旨

本程序核心在于通过 海伦公式(Heron’s Formula) 计算边长分别为 a,b,ca, b, c 的三角形面积:

  1. f(a, b, c) 函数:先计算半周长 s=a+b+c2s = \frac{a+b+c}{2},随后使用公式 S=s(sa)(sb)(sc)S = \sqrt{s(s-a)(s-b)(s-c)} 计算并返回三角形面积。
  2. main 函数
    • cout.flags(ios::fixed)cout.precision(4) 组合使用,控制标准输出流以 定点小数格式(fixed-point) 精确保留 4 位小数
    • 读取三个正整数 a,b,ca, b, c,调用 f(a,b,c) 并输出计算结果。

❓ 判断题

1. (2分)当输入为 2 2 2 时,输出为 1.7321 ( )

A. 正确
B. 错误
正确答案: A

深度解析:
a=2,b=2,c=2a = 2, b = 2, c = 2 时,该三角形为边长为 2 的等边三角形。

  • 半周长 s=2+2+22=3s = \frac{2 + 2 + 2}{2} = 3
  • 面积 S=3×(32)×(32)×(32)=31.7320508...S = \sqrt{3 \times (3-2) \times (3-2) \times (3-2)} = \sqrt{3} \approx 1.7320508...
  • 由于程序指定保留 4 位小数,对 1.73205...1.73205... 进行四舍五入后结果为 1.7321。 因此本题说法正确。

2. (2分)将第 7 行中的 (s-b)*(s-c) 改为 (s-c)*(s-b) 不会影响程序运行的结果 ( )

A. 正确
B. 错误
正确答案: A

深度解析:
在实数乘法运算中,乘法满足 交换律,即对任意实数 x,yx, y,均有 x×y=y×xx \times y = y \times x。 因此,将 (s-b)*(s-c) 改写为 (s-c)*(s-b) 表达式的代数含义和数值计算结果完全一致,不会对程序运行结果产生任何影响。故本题说法正确。


3. (2分)程序总是输出四位小数 ( )

A. 正确
B. 错误
正确答案: A

深度解析:
在 C++ iostream 中:

  • cout.flags(ios::fixed) 强制浮点数采用定点小数表示法输出;
  • cout.precision(4) 设置浮点数的显示精度为 4 位。 两者结合后,即使计算结果为整数(如 6 或 30),cout 也会在末尾自动补零输出 6.000030.0000。由于题目假设输入的数均为不超过 1000 的正整数,输出始终为浮点数且固定展示 4 位小数。因此本题说法正确。

❓ 单选题

4. 当输入为 3 4 5 时,输出为 ( )

A. 6.0000
B. 12.0000
C. 24.0000
D. 30.0000
正确答案: A

深度解析:
3,4,53, 4, 5 是一组经典的勾股数,构成的三角形为直角三角形(直角边长为 3 和 4)。

  • 面积计算S=12×3×4=6S = \frac{1}{2} \times 3 \times 4 = 6
  • 海伦公式校验s=3+4+52=6s = \frac{3+4+5}{2} = 6S=6×(63)×(64)×(65)=6×3×2×1=36=6S = \sqrt{6 \times (6-3) \times (6-4) \times (6-5)} = \sqrt{6 \times 3 \times 2 \times 1} = \sqrt{36} = 6。 配合 4 位小数的格式化输出,最终输出结果为 6.0000

5. 当输入为 5 12 13 时,输出为 ( )

A. 24.0000
B. 30.0000
C. 60.0000
D. 120.0000
正确答案: B

深度解析:
5,12,135, 12, 13 同样是一组标准的勾股数(52+122=25+144=169=1325^2 + 12^2 = 25 + 144 = 169 = 13^2),构成的三角形为直角三角形(直角边长为 5 和 12)。

  • 面积计算S=12×5×12=30S = \frac{1}{2} \times 5 \times 12 = 30
  • 海伦公式校验s=5+12+132=15s = \frac{5+12+13}{2} = 15S=15×(155)×(1512)×(1513)=15×10×3×2=900=30S = \sqrt{15 \times (15-5) \times (15-12) \times (15-13)} = \sqrt{15 \times 10 \times 3 \times 2} = \sqrt{900} = 30。 配合 4 位小数格式化输出,结果为 30.0000,对应 B 选项。

📍 第二题:最长公共子序列(LCS)与字符串循环匹配

💻 源码展示

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

int f(string x,string y){
    int m=x.size();
    int n=y.size();
    vector<vector<int>>v(m+1,vector<int>(n+1,0));
    for(int i=1;i<=m;i++){
        for(int j=1;j<=n;j++){
            if(x[i-1]==y[j-1]){
                v[i][j]=v[i-1][j-1]+1;
            }else{
                v[i][j]=max(v[i-1][j],v[i][j-1]);
            }
        }
    }
    return v[m][n];
}
bool g(string x,string y){
    if(x.size() != y.size()){
        return false;
    }
    return f(x+x,y)==y.size();
}
int main(){
    string x,y;
    cin>>x>>y;
    cout<<g(x,y)<<endl;
    return 0;
}

💡 程序主旨

  1. f(x, y) 函数:这是求解 最长公共子序列(Longest Common Subsequence, LCS) 的经典动态规划算法。
    • mn 分别为字符串 xy 的长度。
    • 二维数组 v[i][j] 表示 x[0...i-1]y[0...j-1] 的最长公共子序列长度。
    • x[i-1] == y[j-1],则 v[i][j] = v[i-1][j-1] + 1;否则 v[i][j] = max(v[i-1][j], v[i][j-1])
    • 函数最终返回 v[m][n],即 xy 的 LCS 长度。
  2. g(x, y) 函数
    • 首先校验 xy 长度是否相等,若不等直接返回 false
    • 若长度相等,则计算 f(x+x, y)。将 x 拼接自身得到 x+x(长度为 2m2m),若 yx+x 的一个子序列且长度等于 y.size(),则返回 true

❓ 判断题

1. f 函数的返回值小于等于 min{n,m}\min\{n, m\}。( )

A. 正确
B. 错误
正确答案: A

深度解析:
f(x, y) 计算的是字符串 x(长度 mm)和 y(长度 nn)的 最长公共子序列长度。 公共子序列中的字符必定来自原字符串,其长度不可能超过参与比较的任何一个字符串的长度。因此 LCS 长度的上界为 min{m,n}\min\{m, n\},本题说法正确。


2. f 函数的返回值等于两个输入字符串的最长公共子串的长度。( )

A. 正确
B. 错误
正确答案: B

深度解析:
在数据结构与算法中:

  • 最长公共子串(Longest Common Substring) 要求字符在原字符串中必须 连续
  • 最长公共子序列(Longest Common Subsequence, LCS) 只要求字符保持 相对顺序,不要求连续。 代码中当 x[i-1] != y[j-1] 时,使用了 v[i][j] = max(v[i-1][j], v[i][j-1]) 继承前面的历史匹配状态,这正是 最长公共子序列 的状态转移方程。因此说法错误。

3. 当输入两个完全相同的字符串时,g 函数的返回值总是 true。( )

A. 正确
B. 错误
正确答案: A

深度解析:
当输入 xxyy 完全相同(即 x=yx = ym=nm = n)时:

  • x.size()==y.size()x.size() == y.size() 满足,不会提前返回 false
  • x+xx+x 包含了完整的原字符串 xx 作为其前半部分前缀。
  • 因为 y=xy = x,所以 yy 显然是 x+xx+x 的一个子序列,因此 f(x+x,y)=y.size()f(x+x, y) = y.size()
  • f(x+x, y) == y.size() 恒成立,g 函数必然返回 true。故本题说法正确。

❓ 单选题

4. 将第 19 行中的 v[m][n] 替换为 v[n][m],那么该程序( )。

A. 行为不变
B. 只会改变输出
C. 一定异常退出
D. 可能异常退出
正确答案: D

深度解析:
f(x, y) 函数内部,二维动态数组 v 声明大小为 v(m+1, vector<int>(n+1, 0)),即共有 m+1m+1 行、n+1n+1 列:

  • 第一维(行)合法下标范围为 0m0 \dots m
  • 第二维(列)合法下标范围为 0n0 \dots n

当我们将返回值改为 v[n][m] 时:

  • 情况一(n>mn > m:第一维下标 nn 超过了数组行数上界 mm,发生 越界访问(Out of Bounds),极易触发 Segmentation Fault / 内存非法访问,导致程序 异常退出
  • 情况二(nmn \le m:第一维下标未越界,程序不会崩溃,但访问了错误的位置(v[n][m] 而非答案所在的 v[m][n]),导致输出错误答案。

综合以上两种情况,该程序表现为 “可能异常退出”,故选 D。


5. 当输入为 csp-j p-jcs 时,输出为( )。

A. 0
B. 1
C. T
D. F
正确答案: B

深度解析:

  1. 参数分析x=x = "csp-j"(长度 5),y=y = "p-jcs"(长度 5)。长度相等。
  2. 拼接字符串x+x=x+x = "csp-jcsp-j"
  3. 匹配子序列:我们在 x+x=x+x = "csp-jcsp-j" 中寻找 y=y = "p-jcs" 的每个字符:
    • 第 1 个字符 'p':出现在 x+x 下标 2 处(cs[p]-jcsp-j
    • 第 2 个字符 '-':出现在 x+x 下标 3 处(csp[-]jcsp-j
    • 第 3 个字符 'j':出现在 x+x 下标 4 处(csp-[j]csp-j
    • 第 4 个字符 'c':出现在 x+x 下标 5 处(csp-j[c]sp-j
    • 第 5 个字符 's':出现在 x+x 下标 6 处(csp-jc[s]p-j) 按顺序依次匹配下标 234562 \rightarrow 3 \rightarrow 4 \rightarrow 5 \rightarrow 6,完美找到了子序列 "p-jcs"
  4. 计算结果:LCS 长度 f(x+x,y)=5=y.size()f(x+x, y) = 5 = y.size()g 函数返回 true
  5. 输出格式:在 C++ 标准库中,cout 默认将布尔值 true 打印为整数 1false 打印为 0)。因此输出为 1

6. 当输入为 csppsc spsccp 时,输出为( )。

A. T
B. F
C. 0
D. 1
正确答案: D

深度解析:

  1. 参数分析x=x = "csppsc"(长度 6),y=y = "spsccp"(长度 6)。长度相等。
  2. 拼接字符串x+x=x+x = "csppsccsppsc"
  3. 匹配子序列:在 x+xx+x 中按顺序查找 y=y = "spsccp" 的每个字符:
    • 第 1 个字符 's':取第 1 个字符(下标 1:c[s]ppsc...
    • 第 2 个字符 'p':取第 2 个字符(下标 2:cs[p]psc...
    • 第 3 个字符 's':取第 4 个字符(下标 4:cspp[s]c...
    • 第 4 个字符 'c':取第 5 个字符(下标 5:cspps[c]...
    • 第 5 个字符 'c':取第 6 个字符(下标 6:csppsc[c]...
    • 第 6 个字符 'p':取第 8 个字符(下标 8:csppsccs[p]...) 匹配到的下标序列为 1<2<4<5<6<81 < 2 < 4 < 5 < 6 < 8,成功拼出 "spsccp"
  4. 计算结果:LCS 长度 f(x+x,y)=6=y.size()f(x+x, y) = 6 = y.size()g 函数返回 truecout 打印出 1

📍 第三题:因子平方和计算与数论推演

💻 源码展示

#include <iostream>
#include <cmath>
using namespace std;

int solve1(int n){
    return n*n;
}

int solve2(int n){
    int sum=0;
    for(int i=1;i<=sqrt(n);i++){
        if(n%i==0){
            if(n/i==i){
                sum+=i*i;
            }else{
                sum+=i*i+(n/i)*(n/i);
            }
        }
    }
    return sum;
}
int main(){
    int n;
    cin>>n;
    cout<<solve2(solve1(n))<<" "<<solve1(solve2(n))<<endl;
    return 0;
}

假设输入的 nn 是绝对值不超过 1000 的整数,完成下面的判断题和单选题:

💡 程序主旨

  1. solve1(n) 函数:计算并返回 n2n^2
  2. solve2(n) 函数:计算正整数 nn所有因子的平方和
    • 使用试除法枚举 i[1,n]i \in [1, \lfloor\sqrt{n}\rfloor]
    • ii 能整除 nn,则 iini\frac{n}{i} 均为 nn 的因子。
    • ni==i\frac{n}{i} == i 时(即 nn 为完全平方数),只累加一次 i2i^2;否则累加 i2+(ni)2i^2 + (\frac{n}{i})^2
  3. main 函数
    • 第一项输出:solve2(solve1(n)),即计算 n2n^2 的所有因子的平方和
    • 第二项输出:solve1(solve2(n)),即计算 nn 的所有因子的平方和)的平方

❓ 判断题

1. 如果输入的 nn 为正整数,solve2 函数的作用是计算 nn 所有的因子的平方和( )

A. 正确
B. 错误
正确答案: A

深度解析:
试除法通过循环 1in1 \le i \le \sqrt{n},找到 nn 的每一对因子 (i,n/i)(i, n/i)。对于每一对因子,代码分别计算了它们的平方 i2i^2(n/i)2(n/i)^2 并进行累加。这种方法完整且不重不漏地计算了正整数 nn 的所有约数(因子)的平方之和。因此说法正确。


2. 第 13 ~ 14 行的作用是避免 nn 的平方根因子 ii(或 n/in/i)进入第 16 行而被计算两次( )

A. 正确
B. 错误
正确答案: A

深度解析:
nn 是一个完全平方数时(例如 n=9n = 9i=3i = 3),满足 n/i==in/i == i。 若不进行第 1314 行的特判直接执行第 16 行 sum += i*i + (n/i)*(n/i),则 323^2 会被加两次(算成 32+32=183^2 + 3^2 = 18)。1314 行的 if(n/i == i) sum += i*i; 正是为了去重,防止平方根因子被计算两次。故本题说法正确。


3. 如果输入的 nn 为质数,solve2(n) 的返回值为 n2+1n^2 + 1( )

A. 正确
B. 错误
正确答案: A

深度解析:
nn 为质数(如 2, 3, 5, 7 …),根据质数的定义,其正因子有且仅有两个:11nn。 因此,solve2(n) 计算的因子平方和为:

solve2(n)=12+n2=n2+1\text{solve2}(n) = 1^2 + n^2 = n^2 + 1

说法完全正确。


❓ 单选题

4. (4分)如果输入的 nn 为质数 pp 的平方,那么 solve2(n) 的返回值为( )

A. p2+p+1p^2 + p + 1
B. n2+n+1n^2 + n + 1
C. n2+1n^2 + 1
D. p4+2p2+1p^4 + 2p^2 + 1
正确答案: B

深度解析:
若输入 n=p2n = p^2(其中 pp 为质数):

  1. nn 的所有正因子为:1,p,p21, p, p^2(共 3 个因子)。
  2. solve2(n) 返回这 3 个因子的平方和: solve2(n)=12+p2+(p2)2=1+p2+p4\text{solve2}(n) = 1^2 + p^2 + (p^2)^2 = 1 + p^2 + p^4
  3. n=p2n = p^2 代入上式可得 p4=(p2)2=n2p^4 = (p^2)^2 = n^2,故: solve2(n)=n2+n+1\text{solve2}(n) = n^2 + n + 1 对应 B 选项。

5. 当输入为正整数时,第一项减去第二项的差值一定( )

A. 大于 0
B. 大于等于 0 且不一定大于 0
C. 小于 0
D. 小于等于 0 且不一定小于 0
正确答案: D

深度解析:
我们需要比较第一项 solve2(solve1(n)) 与第二项 solve1(solve2(n)) 的大小关系。 设第一项为 A=solve2(n2)A = \text{solve2}(n^2),第二项为 B=(solve2(n))2B = (\text{solve2}(n))^2

我们通过代入具体数字校验:

  • n=1n = 1
    • n2=1n^2 = 1solve2(1)=12=1\text{solve2}(1) = 1^2 = 1,故 A=1A = 1
    • solve2(1)=1\text{solve2}(1) = 1(solve2(1))2=12=1(\text{solve2}(1))^2 = 1^2 = 1,故 B=1B = 1
    • 差值 AB=11=0A - B = 1 - 1 = 0
  • n=2n = 2
    • 第一项 A=solve2(22)=solve2(4)A = \text{solve2}(2^2) = \text{solve2}(4)44 的因子为 1,2,41, 2, 4,其平方和 A=12+22+42=1+4+16=21A = 1^2 + 2^2 + 4^2 = 1 + 4 + 16 = 21
    • 第二项 B=(solve2(2))2B = (\text{solve2}(2))^222 的因子为 1,21, 2,其平方和为 12+22=51^2 + 2^2 = 5,故 B=52=25B = 5^2 = 25
    • 差值 AB=2125=4<0A - B = 21 - 25 = -4 < 0

综上所述,差值可以等于 0(当 n=1n = 1 时),也可以小于 0(当 n>1n > 1 时)。因此差值 小于等于 0 且不一定小于 0,对应 D 选项。


6. 当输入为 5 时,输出为( )

A. 651 625
B. 650 729
C. 651 676
D. 652 625
正确答案: C

深度解析:
当输入 n=5n = 5 时:

  1. 计算第一项 solve2(solve1(5))
    • solve1(5) =52=25= 5^2 = 25
    • solve2(25)2525 的因子有 1,5,251, 5, 25
    • 第一项 =12+52+252=1+25+625=651= 1^2 + 5^2 + 25^2 = 1 + 25 + 625 = \mathbf{651}
  2. 计算第二项 solve1(solve2(5))
    • solve2(5)55 是质数,因子有 1,51, 5,其平方和 =12+52=26= 1^2 + 5^2 = 26
    • solve1(26) =262=676= 26^2 = \mathbf{676}
  3. 拼装输出:程序输出用空格分隔的两项,结果为 651 676。对应 C 选项。

💡 总结与解题技巧

在 CSP-J 阅读程序题中,拿到高分的核心在于:

  1. 宏观理解程序目的:切忌一上来就机械盲目代入数据计算。先看包含哪些函数,根据关键公式(如海伦公式、LCS 动态规划、试除法)判断程序功能,能大幅提升答题效率。
  2. 注意语言与输出细节:如 C++ 浮点输出格式 fixed + precisionbool 变量用 cout 输出的默认格式(10)、vector/数组下标越界对程序的影响等。
  3. 擅用特殊值与特例代入:在做性质判断与公式单选题(如第 3 大题第 5 小题)时,构造 n=1,n=2n=1, n=2 等小样本数据快速验证,是避开复杂证明、高准确率解题的捷径!

本文由coderli.com原创,按照CC BY-NC-SA 4.0 进行授权

所有代码已上传至Github:https://github.com/lihongzheshuai/yummy-code

luogu-”系列题目可在 洛谷题库 在线评测。

bcqm-”系列题目可在 编程启蒙题库 在线评测。

GESP/CSP认证交流QQ群: 688906745

GESP/CSP 认证学习微信公众号
GESP/CSP 认证学习微信公众号
最后更新于