乐于分享
好东西不私藏

GESP 2 级典型真题

GESP 2 级典型真题

GESP 2 级典型真题

1. 自幂数判断

题目描述

自幂数是指,一个  位数,满足各位数字  次方之和等于本身。

例如:

  • • 153 是 3 位数,,所以 153 是自幂数
  • • 1634 是 4 位数,,所以 1634 是自幂数

现在输入若干个正整数,判断它们是否是自幂数。

输入格式

第一行 ,表示有  个待判断的数()。
接下来  行,每行一个正整数(均小于 )。

输出格式

输出  行,对应每个数:是自幂数输出 T,否则输出 F

样例

输入



1
2
3
4

3
153
123
1634


输出



1
2
3

T
F
T


思路分析

判断一个数是不是自幂数就三步:

  1. 1. 先看这个数有多少位(比如 153 有 3 位)
  2. 2. 把每一位拆出来,算"这一位的 位数次方"再累加
  3. 3. 看累加结果等不等于原数

数位分离是核心操作——用 % 10 取个位,用 / 10 去掉个位,循环到 0 为止。

⚠️ 不能用 ^ 算次方!C++ 里 ^ 是异或,要自己写循环乘。

写法一:不用数组(边拆边算)

先统计位数,再拆一遍算累加和,全程用变量存。



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

#include <bits/stdc++.h>
using namespace std;

int main(){
    int m;
    cin >> m;
    while (m--) {
        int x;
        cin >> x;
        int original = x;

        // 1. 统计位数
        int n = 0, t = x;
        while (t > 0) {
            n++;
            t /= 10;
        }

        // 2. 拆位计算
        int sum = 0;
        t = x;
        while (t > 0) {
            int d = t % 10;       // 取出个位
            int p = 1;
            for (int i = 0; i < n; i++) p *= d;  // 算 d^n
            sum += p;
            t /= 10;              // 去掉个位
        }

        // 3. 判断
        if (sum == original) cout << "T\n";
        else                 cout << "F\n";
    }
    return 0;
}


写法二:用数组(先把每一位存起来)

用数组存下每一位数字,然后统一计算。



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

#include <bits/stdc++.h>
using namespace std;

int main(){
    int m;
    cin >> m;
    while (m--) {
        int x;
        cin >> x;
        int original = x;

        // 1. 把每一位存到数组里
        int digits[10], n = 0;      // 10^8 < 10 位,数组开够
        while (x > 0) {
            digits[n++] = x % 10;   // 存个位,然后 n+1
            x /= 10;
        }
        // 此时 digits[0] 是个位,digits[1] 是十位……

        // 2. 遍历数组,累加每位数字的 n 次方
        int sum = 0;
        for (int i = 0; i < n; i++) {
            int p = 1;
            for (int j = 0; j < n; j++) p *= digits[i];
            sum += p;
        }

        // 3. 判断
        if (sum == original) cout << "T\n";
        else                 cout << "F\n";
    }
    return 0;
}


两种写法对比

对比项
不用数组
用数组
思路
边拆边算,拆两遍
先存后算,拆一遍
空间
只需几个变量
多一个数组
适用场景
只需一次遍历
后面还要用每位数字

GESP 2 级两种写法都能得分,用数组更直观。

常见易错点

错误
说明
用 ^ 算次方
^
 是异或!老老实实写 for 循环
改丢了原数
统计位数时 x 变成 0 了,后面没得比 → 记得用临时变量
数组越界
 最多 9 位,digits[10] 完全够,但别写成 digits[9]

2. 495 问题(卡布列克运算)

题目描述

给定一个三位数,要求各位数字不相同。例如 352 符合要求,112 不符合。

将这个三位数的三个数字重新排列:

  • • 得到的最大数减去得到的最小数,形成一个新的三位数
  • • 对这个新的三位数重复上述过程

神奇的是,最终一定会得到 495

例如:

  • • 352 → 最大 532,最小 235,差 297
  • • 297 → 最大 972,最小 279,差 693
  • • 693 → 最大 963,最小 369,差 594
  • • 594 → 最大 954,最小 459,差 495 ✓

所以 352 经过 4 次变换得到 495。

现在输入一个三位数,求经过多少次变换能够得到 495。

输入格式

一行,一个符合要求的三位数 (各位数字不同)。

输出格式

一行,一个整数 ,表示经过  次变换得到 495。

样例

输入



1

352


输出



1

4


思路分析

核心操作:数位分离 → 排序找最大最小 → 求差 → 循环直到等于 495

  1. 1. 拆出三位数字:用 % 10 和 / 10 分离个位、十位、百位
  2. 2. 找最大/最小排列
    • • 最大数:数字从大到小排列
    • • 最小数:数字从小到大排列
  3. 3. 计算差值:最大 - 最小
  4. 4. 计数循环:直到差等于 495 为止

数学保证:对任意各位不同的三位数,最多 7 次内必得到 495。

代码实现



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

#include <bits/stdc++.h>
using namespace std;

int main(){
    int n;
    cin >> n;

    int cnt = 0;  // 计数器

    while (n != 495) {
        // 1. 分离三个数字
        int a = n / 100;       // 百位
        int b = n / 10 % 10;   // 十位
        int c = n % 10;        // 个位

        // 2. 用 max/min 找最大和最小数字
        int mx = max(a, max(b, c));  // 最大数字
        int mn = min(a, min(b, c));  // 最小数字
        int mid = a + b + c - mx - mn;  // 中间数字(总和减最大最小)

        // 3. 组成最大数和最小数
        int maxNum = mx * 100 + mid * 10 + mn;  // 大→中→小
        int minNum = mn * 100 + mid * 10 + mx;  // 小→中→大

        // 4. 计算差值,继续循环
        n = maxNum - minNum;
        cnt++;
    }

    cout << cnt << endl;
    return 0;
}


扩展知识

这个现象叫 Kaprekar's Routine(卡布列克运算)。

  • • 三位数最终都会收敛到 495
  • • 四位数最终都会收敛到 6174(也叫 Kaprekar 常数)
  • • 你可以试试:输入 6174,最大 7641 减最小 1467 还是 6174,所以 6174 是"不动点"

3. 小杨做题(斐波那契变体)

题目描述

为了准备考试,小杨每天都要做题:

  • • 第 1 天做  道题
  • • 第 2 天做  道题
  • • 从第 3 天起,每天做的题目数量 = 前两天的总和(类似斐波那契数列)

附加规则:如果某一天做了 大于或等于  题,则接下来的所有日子里他再也不做题了。

问:到了第  天,小杨总共做了多少题?

输入格式

共 4 行,每行一个整数:

  • • 第 1 行:(第 1 天做题数)
  • • 第 2 行:(第 2 天做题数)
  • • 第 3 行:(终止阈值)
  • • 第 4 行:(总天数)

数据范围

  • • 
  • • 
  • • 

输出格式

一行一个整数,表示小杨  天里总共做了多少题目。

样例

输入



1
2
3
4

1
2
10
5


输出



1

19


样例解释

天数
做题数
计算方式
是否 ≥ 10?
1
1
给定
2
2
给定
3
3
1+2=3
4
5
2+3=5
5
8
3+5=8

总和:

注意:第 5 天做了 8 题,8 < 10,所以没有触发停止条件。

思路分析

这是一个带终止条件的递推问题,核心是:

  1. 1. 斐波那契递推第 i 天 = 第 i-1 天 + 第 i-2 天
  2. 2. 终止判断:如果某天做题数 ≥ m,则之后天数都算作 0(不做题了)
  3. 3. 累加求和:把每天做的题数累加起来

关键点:终止条件一旦触发,后续所有天数贡献都是 0,所以可以在循环里加判断提前退出。

代码实现



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

#include <bits/stdc++.h>
using namespace std;

int main(){
    int a, b, m, N;
    cin >> a;
    cin >> b;
    cin >> m;
    cin >> N;

    long long sum = 0;      // 总和,用 long long 保险

    // 第 1 天
    if (a >= m) {
        sum += a;
        // 已经触发终止,后面不做题了
    } else {
        sum += a;

        // 第 2 天
        if (b >= m) {
            sum += b;
        } else {
            sum += b;

            // 从第 3 天开始递推
            int day1 = a;   // 前一天
            int day2 = b;   // 当前天(相对于循环里的"昨天")

            for (int i = 3; i <= N; i++) {
                int today = day1 + day2;  // 今天做题数 = 前两天之和

                if (today >= m) {
                    // 触发终止,今天已经做了,后面不做
                    sum += today;
                    break;
                } else {
                    sum += today;
                    // 继续递推
                    day1 = day2;
                    day2 = today;
                }
            }
        }
    }

    cout << sum << endl;
    return 0;
}


更简洁的写法(滚动变量)



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

#include <bits/stdc++.h>
using namespace std;

int main(){
    int a, b, m, N;
    cin >> a >> b >> m >> N;

    long long sum = a + b;  // 前两天总和

    // 如果前两天已经触发终止,后面不做题
    if (a >= m || b >= m) {
        // 但题目保证 a,b < m,所以这行其实可以省
        cout << sum << endl;
        return 0;
    }

    int prev2 = a;  // 前两天
    int prev1 = b;  // 前一天

    for (int i = 3; i <= N; i++) {
        int cur = prev1 + prev2;  // 今天

        if (cur >= m) {
            sum += cur;   // 今天做了
            break;        // 之后不做了
        }

        sum += cur;
        // 滚动更新
        prev2 = prev1;
        prev1 = cur;
    }

    cout << sum << endl;
    return 0;
}


易错点分析

错误
说明
终止条件理解错
"≥ m 之后不做"是指当天做了,但之后不做,不是当天也不做
数据类型溢出
虽然题目数据小,但斐波那契增长快,建议用 long long
滚动变量更新错
注意 prev2 = prev1; prev1 = cur; 的顺序,别搞反
忘记 break
触发终止后要用 break 退出循环,否则继续算

知识点关联

概念
在本题中的体现
斐波那契数列
cur = prev1 + prev2
 的递推关系
滚动变量
只用两个变量保存历史状态,节省空间
条件终止
if (cur >= m) break;
 控制循环结束
累加求和
边算边累加,避免二次遍历

4. 平方数判断

题目描述

给定  个正整数 ,判断每一个  是否可以表示为两个正整数的平方和

即:是否存在正整数 ),使得 

输入格式

第一行一个正整数 )。
接下来  行,每行一个正整数 )。

输出格式

输出  行,每行一个大写字母:能表示为两个正整数的平方和输出 Y,否则输出 N

样例

输入



1
2
3
4
5

4
5
4
3
25


输出



1
2
3
4

Y
N
N
Y


样例解释

  • •  → Y
  • • ,没有两个正整数的平方和等于 4 → N
  • • :没有 → N
  • •  → Y

思路分析

题目要求 ,所以直接枚举 

  1. 1.  从 1 开始枚举到 (因为 
  2. 2. 计算 
  3. 3. 判断  是否是完全平方数,并且 

判断一个数  是否为完全平方数的方法:



1
2

int y = (int)sqrt(t);
if (y * y == t) { /* t 是完全平方数 */ }


注意: 用 sqrt() 函数,需要 #include <cmath><bits/stdc++.h> 已包含)。

代码实现



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

#include <bits/stdc++.h>
using namespace std;

int main(){
    int n;
    cin >> n;
    while (n--) {
        int x;
        cin >> x;
        bool ok = false;
        int limit = (int)sqrt(x);
        for (int a = 1; a <= limit; a++) {
            int remain = x - a * a;
            if (remain <= 0) continue;
            int b = (int)sqrt(remain);
            if (b * b == remain && b >= 1) {
                ok = true;
                break;
            }
        }
        if (ok) cout << "Y\n";
        else    cout << "N\n";
    }
    return 0;
}


常见易错点

错误
说明
忘了 sqrt() 记得强转 int
sqrt()
 返回 double,需要 (int)sqrt(x) 取整数部分
完全平方判断写错
用 b * b == remain 而不是 sqrt(remain) == (int)sqrt(remain)(浮点误差)
漏掉 y >= 1
题目要求正整数, 不算。 本身是平方数时要注意
枚举范围
x
 枚举到  就够了,不需要到 

扩展知识

能表示为两个正整数平方和的数有一个有趣的规律:

  • • 质数  能表示为两平方和  或 费马平方和定理
  • • 例如:
  • • 而  这些  的质数不能

5. N字矩阵

题目描述

构造一个  的 N 字矩阵( 为奇数),满足:

  • • 从左上角到右下角的对角线是 +
  • • 第 1 列和第  列都是 +
  • • 其余位置都是 -

例如, 的 N 字矩阵:



1
2
3
4
5

+---+
++--+
+-+-+
+--++
+---+


输入格式

一行,一个正整数  为奇数)。

输出格式

输出对应的 N 字矩阵。

思路分析

这是一个图形打印问题,关键是判断每个位置应该输出 + 还是 -

对于第  行第  列(从 1 开始计数):

  • • + 的条件:(第一列)或 (最后一列)或 (对角线)
  • • 否则输出 -

代码实现



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

#include <bits/stdc++.h>
using namespace std;

int main(){
    int m;
    cin >> m;
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= m; j++) {
            // 第一列、最后一列、对角线 都是 +
            if (j == 1 || j == m || i == j)
                cout << '+';
            else
                cout << '-';
        }
        cout << endl;
    }
    return 0;
}


常见易错点

错误
说明
行列搞混
i
 是行,j 是列,对角线是 i == j
忘记换行
每行输出后要 cout << endl
坐标从0开始
如果用 for (int i = 0; i < m; i++),对角线是 i == j,第一列是 j == 0

6. 数三角形

题目描述

直角三角形有两条直角边 ,面积为 

计算当  均取不超过  的正整数时,有多少个不同的直角三角形,其面积为整数。

注意 和  视为同一个三角形。

输入格式

一行,一个整数 )。

输出格式

输出不同直角三角形的数量。

样例

输入 1



1

3


输出 1



1

3


输入 2



1

5


输出 2



1

9


样例解释(n=3)

面积为整数  为偶数  中至少有一个是偶数。

有效组合(设 ):

  • • : 面积 = 1
  • • : 面积 = 2
  • • : 面积 = 3

共 3 个。

思路分析

面积为整数  是偶数 

即  和  中至少有一个是偶数

为了避免重复计数( 和  相同),我们规定 ,然后枚举:



1
2
3

for (int a = 1; a <= n; a++)
    for (int b = a; b <= n; b++)  // b 从 a 开始,保证 a <= b
        if (a * b % 2 == 0) cnt++;


代码实现



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

#include <bits/stdc++.h>
using namespace std;

int main(){
    int n;
    cin >> n;
    int cnt = 0;
    for (int a = 1; a <= n; a++) {
        for (int b = a; b <= n; b++) {  // b 从 a 开始,避免重复
            if ((long long)a * b % 2 == 0)  // 面积为整数
                cnt++;
        }
    }
    cout << cnt << endl;
    return 0;
}


常见易错点

错误
说明
重复计数
没有用 b = a 而是 b = 1,导致  和  算两次
整数溢出
 可能超过 int 范围,用 long long 或先 % 2
条件写错
a * b % 2 == 0
 才是面积为整数,不是 == 1

7. 幂和数

题目描述

对于正整数 ,如果  可以表示为两个 2 的次幂之和:


那么称  为幂和数

给定 ,求区间  中有多少个幂和数。

输入格式

一行,两个正整数 )。

输出格式

输出幂和数的数量。

样例

输入 1



1

2 8


输出 1



1

6


输入 2



1

10 100


输出 2



1

20


样例解释(2~8)

2 的幂:1, 2, 4, 8, 16, ...

幂和数 = 两个 2 的幂之和(可以相同):

  • • 
  • • 
  • • 
  • • 

在  范围内:2, 3, 4, 5, 6, 8(注意 7 和 9 不在这个区间内),共 6 个。

思路分析

方法:预处理所有可能的幂和数

  1. 1. 先列出所有 2 的幂(不超过 ):1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192
  2. 2. 两重循环枚举 ,用数组标记哪些是幂和数
  3. 3. 统计  范围内的数量

注意: 可以等于 ,即  也是幂和数。

代码实现



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

#include <bits/stdc++.h>
using namespace std;

int main(){
    // 1. 预处理 2 的幂(不超过 10000)
    vector<int> powers;
    for (int p = 1; p <= 10000; p *= 2)
        powers.push_back(p);

    // 2. 标记所有幂和数
    bool isSum[10001] = {false};
    int n_pow = powers.size();
    for (int i = 0; i < n_pow; i++) {
        for (int j = i; j < n_pow; j++) {  // j 从 i 开始,避免重复
            int sum = powers[i] + powers[j];
            if (sum <= 10000)
                isSum[sum] = true;
        }
    }

    // 3. 读入并统计
    int l, r;
    cin >> l >> r;
    int cnt = 0;
    for (int i = l; i <= r; i++)
        if (isSum[i]) cnt++;

    cout << cnt << endl;
    return 0;
}