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. 先看这个数有多少位(比如 153 有 3 位) 2. 把每一位拆出来,算"这一位的 位数次方"再累加 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 循环 |
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. 拆出三位数字:用 % 10和/ 10分离个位、十位、百位2. 找最大/最小排列: • 最大数:数字从大到小排列 • 最小数:数字从小到大排列 3. 计算差值:最大 - 最小 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
样例解释
总和:
注意:第 5 天做了 8 题,8 < 10,所以没有触发停止条件。
思路分析
这是一个带终止条件的递推问题,核心是:
1. 斐波那契递推: 第 i 天 = 第 i-1 天 + 第 i-2 天2. 终止判断:如果某天做题数 ≥ m,则之后天数都算作 0(不做题了) 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;
}
易错点分析
long long | |
prev2 = prev1; prev1 = cur; 的顺序,别搞反 | |
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 开始枚举到 (因为 ) 2. 计算 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;
}
常见易错点
ij 是列,对角线是 i == j | |
cout << endl | |
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. 先列出所有 2 的幂(不超过 ):1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192 2. 两重循环枚举 ,用数组标记哪些是幂和数 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;
}
夜雨聆风