GESP2025年12月认证C++五级真题与解析(编程题2 (相等序列))

一、先看原题


二、题目解析:

🏰《相等序列》------数字王国的"统一魔法"✨


1、故事背景 🌟

数字王国 里,住着 n 个数字小精灵:

数字国王说:

👑"太乱了!

我要你们 全部变成一样的数字!"


🔮 小 A 会的两种魔法(每次 1 个金币)

对任意一个数字 x,可以:

🪄 魔法一:变大
cpp 复制代码
x → x × p   (p 是任意质数)

👉 比如:

  • 6 → 6 × 5 = 30

  • 10 → 10 × 3 = 30


✂️ 魔法二:变小
cpp 复制代码
x → x ÷ p   (p 是质数,而且 p 必须能整除 x)

👉 比如:

  • 30 → 30 ÷ 3 = 10

  • 30 → 30 ÷ 5 = 6


❓ 题目要问什么?

👉 最少要花多少金币

👉 才能让所有数字 一模一样


2、换个角度看问题(超级关键)🧠

❌ 错误想法(孩子常犯)

  • 直接去算 "变成多少?"

  • 一个一个试最终数字

❌ 不现实!数字会变得超级大!


✅ 正确想法:拆开数字的"零件"

我们要认识一个重要角色:

🧱【质因数】

👉 任何数字,都是由 质数小积木 拼成的!


🧩 举例(一定要看)

10 是怎么来的?
cpp 复制代码
10 = 2 × 5

6 呢?

cpp 复制代码
6 = 2 × 3

35 呢?

cpp 复制代码
35 = 5 × 7

💡 魔法的本质其实是:

  • ✂️ 除以质数 → 拿掉一块积木

  • 🪄 乘以质数 → 加一块积木


3、真正的问题是什么?🤔

要让所有数字一样

⇨ 它们的 每一种质数积木数量都要一样

我们可以 对每一种质数,单独考虑!


4、讲解样例🚀

输入:

cpp 复制代码
5
10 6 35 105 42

5、以"质数 2"为例 🧮

我们只看 2 这块积木

数字 质因数分解 2 的个数
10 2×5 1
6 2×3 1
35 5×7 0
105 3×5×7 0
42 2×3×7 1

👉 得到指数数组:

cpp 复制代码
[1, 1, 0, 0, 1]

❓ 现在问题变成了什么?

把这 5 个数字

通过 +1 或 -1

变成 同一个数字

最少花几步?


6、神奇结论:选"中位数"最省钱 🎯

👉 排序:

cpp 复制代码
0 0 1 1 1

👉 中位数是:1


为什么选中位数?

  • 左边加

  • 右边减

  • 总步数最少

这在数学上是一个非常重要的结论,但孩子可以记一句话:

🧠 大家向中间靠拢,走的总路最少!


对质数 2 的金币花费:

cpp 复制代码
|1-1| + |1-1| + |0-1| + |0-1| + |1-1|
= 0 + 0 + 1 + 1 + 0
= 2

7、对所有质数都这样做 🏗️

程序会:

1️⃣ 枚举每一个可能出现的质数

2️⃣ 统计它在所有数字中出现了几次

3️⃣ 找"中位数次数"

4️⃣ 累加所有差值


🌟 最终答案:

8

也就是 最少要 8 个金币


8、参考程序:

cpp 复制代码
#include <iostream>
using namespace std;
const int N = 100010;
int num[N][20];
int n, a[N];
void calc_prime_factor(int x){
	for(int i=2;i*i<=x;i++){
		if(x%i==0){
			int cnt=0;
			while(x%i==0){
				x/=i;
				cnt++;
			}
			num[i][cnt]++;
		}
	}
	if(x>1){
		num[x][1]++;
	}
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		calc_prime_factor(a[i]);
	}
	long long ans=0;
	for(int i=2;i<100001;i++){
		int pos = 0;
		for(int j=0;j<20;j++){
			pos += num[i][j];
		}
		num[i][0]=n-pos;
		int median_exponent=0;
		pos = 0;
		for(int j=0;j<20;j++){
			pos += num[i][j];
			if(pos*2>=n){
				median_exponent=j;
				break;
			}
		}
		for(int j=0;j<20;j++){
			ans+=num[i][j]*abs(j-median_exponent);
		}
	}	
	printf("%lld\n",ans);
}

9、参考程序详细讲解 🧑‍💻

(1) 一句话讲解程序🌈

🌟 每个数字都是用"质数积木"搭出来的

我们要让所有数字一模一样

就要让每一种质数积木的数量都一样

最省金币的方法是:

👉 大家向"中间数量"靠拢(中位数)


(2) 全局变量在干什么?📦

cpp 复制代码
const int N = 100010;
int num[N][20];
int n, a[N];

🧺 含义解释

a[i]

👉 第 i 个数字小精灵的值


num[p][k]

👉 一个统计表

意思是:

有多少个数字

👉 质数 p 出现了 k

📌 举例:

如果有 3 个数字里:

  • 2 出现 1 次

  • 2 出现 2 次

  • 2 出现 0 次

那就是:

cpp 复制代码
num[2][1] = 1
num[2][2] = 1
num[2][0] = 1

(3) 这一段是【分解质数】🧱✨(非常重要)

📌 函数:calc_prime_factor

cpp 复制代码
void calc_prime_factor(int x){
	for(int i=2;i*i<=x;i++){
		if(x%i==0){
			int cnt=0;
			while(x%i==0){
				x/=i;
				cnt++;
			}
			num[i][cnt]++;
		}
	}
	if(x>1){
		num[x][1]++;
	}
}

🧠 这个函数在干什么?

👉 把数字 x 拆成:

cpp 复制代码
x = 质数 × 质数 × 质数......

就像把一个乐高城堡拆成:

  • 红色积木

  • 蓝色积木

  • 黄色积木


🪜 拆解步骤

① 从 2 开始试除
cpp 复制代码
for(int i=2;i*i<=x;i++)

意思是:

"我们来试试 2、3、4、5......

看谁是 x 的'积木'"


② 找到一个质数积木
cpp 复制代码
if(x%i==0)

说明:

ix 的一块积木!


③ 数一数有多少块这种积木
cpp 复制代码
while(x%i==0){
	x/=i;
	cnt++;
}

👉 比如:

cpp 复制代码
12 = 2 × 2 × 3

那:

cpp 复制代码
cnt = 2(两个 2)

④ 记到统计表里

cpp 复制代码
num[i][cnt]++;

👉 表示:

有一个数字

👉 用了 cnti


⑤ 最后剩下的大质数
cpp 复制代码
if(x>1){
	num[x][1]++;
}

比如:

cpp 复制代码
23

当 23 除完后,x=23

👉 23 也是一个质数积木!


4、主函数:读入 + 拆数字 👷‍♂️

cpp 复制代码
scanf("%d",&n);
for(int i=1;i<=n;i++){
	scanf("%d",&a[i]);
	calc_prime_factor(a[i]);
}

👉 意思是:

1️⃣ 读入数字个数

2️⃣ 每读一个数字

3️⃣ 马上把它拆成质数积木

4️⃣ 记到 num 表里


5、这一段:按"每一种质数"来算 💡

cpp 复制代码
for(int i=2;i<100001;i++){

👉 意思是:

我们一个质数一个质数地来看

比如:2、3、5、7、11......


6、补齐"没出现的积木"🧩

cpp 复制代码
int pos = 0;
for(int j=0;j<20;j++){
	pos += num[i][j];
}
num[i][0]=n-pos;

🧠 为什么要这一段?

有些数字:

  • 根本 没有质数 i

  • 比如:35 里没有 2

👉 这些也要算!

所以:

  • 总共有 n 个数字

  • 已统计的只有 pos

  • 剩下的就是 0


7、这一段是【找中位数】🎯✨(核心思想)

cpp 复制代码
int median_exponent=0;
pos = 0;
for(int j=0;j<20;j++){
	pos += num[i][j];
	if(pos*2>=n){
		median_exponent=j;
		break;
	}
}

🧠 这段在干什么?(孩子版)

👉 我们要选一个"大家都愿意靠拢的积木数量"

这个数量就是:

🌟 中位数!


🧩 为什么中位数最省金币?

  • 积木多的 → 拆掉

  • 积木少的 → 加上

大家往中间走,走的总步数最少


📦 pos*2 >= n 的意思

"已经有一半以上的数字

不比当前 j 小了"

👉 当前 j 就是中位数!


8、最后一步:统计金币 💰✅

cpp 复制代码
for(int j=0;j<20;j++){
	ans+=num[i][j]*abs(j-median_exponent);
}

🧠 这句话的意思

  • num[i][j] 个数字

  • 它们的质数 ij

  • 目标是 median_exponent

👉 每差 1,就要 1 个金币


🧮 举例(质数 2)

cpp 复制代码
当前:0 0 1 1 1
中位数:1

金币数:

cpp 复制代码
|0-1| + |0-1| + |1-1| + |1-1| + |1-1|

🔍 calc_prime_factor(x) 在干嘛?

👉 把数字 x 拆成:

cpp 复制代码
x = p1^a × p2^b × ...

👉 并统计:

cpp 复制代码
num[质数][指数]++

9、为什么要对"所有质数"都这样算?🔄

因为:

所有数字要完全一样

必须在 每一种质数积木上都一样

👉 所以金币要 全部加起来


10、程序详细注释:

cpp 复制代码
#include <iostream>
#include <cstdio>
using namespace std;

/*
  最大数字范围
  N 用来表示:最多会用到的质数范围
*/
const int N = 100010;

/*
  num[p][k] 的含义(非常重要):

  表示:
  👉 有多少个数字
  👉 质数 p 出现了 k 次

  比如:
  num[2][1] = 3
  说明:有 3 个数字里,质数 2 出现了 1 次
*/
int num[N][20];

/*
  n :数字个数
  a[i] :第 i 个数字
*/
int n, a[N];

/*
  函数作用:把一个数字 x 拆成"质数积木"
  并把结果记录到 num 表中
*/
void calc_prime_factor(int x) {

    // 从 2 开始,尝试用每个数去除 x
    // 只需要试到 sqrt(x)
    for (int i = 2; i * i <= x; i++) {

        // 如果 i 能整除 x,说明 i 是一个质数积木
        if (x % i == 0) {

            int cnt = 0; // 记录这个质数出现了几次

            // 一直除,直到不能再除
            while (x % i == 0) {
                x /= i;
                cnt++;     // 多拆下一块积木
            }

            // 记录:有一个数字,质数 i 出现了 cnt 次
            num[i][cnt]++;
        }
    }

    // 如果最后还剩下一个大于 1 的数
    // 那它本身就是一个质数(只出现 1 次)
    if (x > 1) {
        num[x][1]++;
    }
}

int main() {

    // 读入数字个数
    scanf("%d", &n);

    // 读入每个数字,并立刻进行质因数分解
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);

        // 把 a[i] 拆成质数积木
        calc_prime_factor(a[i]);
    }

    long long ans = 0; // 最终答案:最少金币数

    /*
      接下来,我们一个"质数一个质数"地处理
      比如:2、3、5、7......
    */
    for (int p = 2; p < 100001; p++) {

        /*
          pos 表示:
          👉 当前质数 p
          👉 已经统计了多少个数字
        */
        int pos = 0;

        // 把所有"出现次数"的数量加起来
        for (int k = 0; k < 20; k++) {
            pos += num[p][k];
        }

        /*
          有些数字里,质数 p 一次都没出现
          它们的出现次数是 0
        */
        num[p][0] = n - pos;

        /*
          接下来:找"中位数出现次数"
          中位数 = 最省金币的目标次数
        */
        int median_exponent = 0;
        pos = 0;

        for (int k = 0; k < 20; k++) {
            pos += num[p][k];

            // 如果已经有一半及以上的数字
            // 出现次数 <= k
            // 那 k 就是中位数
            if (pos * 2 >= n) {
                median_exponent = k;
                break;
            }
        }

        /*
          统计金币:
          每一个数字:
          当前次数 -> 中位数次数
          差多少,就花多少金币
        */
        for (int k = 0; k < 20; k++) {
            ans += (long long)num[p][k] * abs(k - median_exponent);
        }
    }

    // 输出最终答案
    printf("%lld\n", ans);

    return 0;
}

三、算法总结 🎒✨


1、这道题"本质上"在干什么?🤔

❌ 它不是在算大数

❌ 也不是在不停试变化

✅ 它真正做的是一件事:

👉 让一群"不一样的东西"
用最少的步数
变成"一样"


2、第一层算法思想:拆开看,而不是一起看 🧩

🧠 常见错误想法:

"我要把每个数字都变成同一个数!"

这会很乱,很难想。


✅ 正确算法思想:

一个复杂问题 → 拆成很多简单问题

在这道题里:

  • 数字很复杂 ❌

  • 但它们是由 质数 组成的 ✅


🌟 于是我们做了第一步:

👉 把每个数字拆成"质数积木"

这一步叫:

问题分解


3、第二层算法思想:每一种质数,单独解决 🔍

🎯 关键观察:

要让所有数字一样

必须满足:

  • 2 的数量一样

  • 3 的数量一样

  • 5 的数量一样

  • ......


🧠 算法思路:

不要一起算
每一种质数,单独算

于是问题变成:

对于某一种质数

👉 让大家的"数量"变成一样

👉 用最少的步数


4、第三层算法思想:往中间靠拢最省力 🎯

🌈 一个非常重要的结论(可以当口诀)

🧠 让大家走到中间,走的总路最短


这就是:

✨ 中位数思想


🧩 在这道题里是什么意思?

  • 有的数字:这个质数很多

  • 有的数字:这个质数很少

  • 我们允许:

    • +1(乘质数)

    • −1(除质数)

👉 每一步花 1 个金币

🎯 所以最省钱的目标是:

👉 中位数个数


5、第四层算法思想:把每一部分的花费加起来 💰

🧠 重要理解:

所有数字要完全一样

必须在 每一种质数上都一样

所以:

  • 对质数 2 的花费

  • 对质数 3 的花费

  • 对质数 5 的花费

  • ......

👉 全部加起来


6、用"算法流程图"总结

《相等序列》解题五步法

1️⃣ 拆数字

把每个数字拆成质数积木

2️⃣ 分质数看

每一种质数单独处理

3️⃣ 统计次数

看看大家有多少块这种积木

4️⃣ 找中位数

大家往中间靠最省金币

5️⃣ 全部加起来

得到最少金币数


7、这道题用了哪些"算法思想"?🎒

算法思想 小学生理解
问题分解 大问题拆成小问题
数论 数字是质数积木
贪心(中位数) 往中间靠最省
统计 数一数有多少
累加 每部分都要算

8、给学生的一句话总结 🌟

🌟 数字虽然很大、很复杂

但它们都是用质数搭出来的

我们只要让
每一种质数的数量都一样

再让大家
往中间的数量靠拢

就能用最少的金币完成任务!


9、这道题教会学生的"通用能力"🚀

真正学完这道题,学生其实学会了:

  • 不怕复杂问题

  • 会"拆问题"

  • 会找"最省力的方法"

  • 明白:

👉 最优解不是乱试出来的,是按照步骤一步一步统计出来的


相关推荐
前端小L19 小时前
双指针专题(九):谁是窗口里的老大?——「滑动窗口最大值」
javascript·算法·双指针与滑动窗口
CAU界编程小白20 小时前
Linux系统编程系列之模拟文件操作
linux·算法
UP_Continue20 小时前
C++--可变参数模板和lambda
开发语言·c++
萤虫之光20 小时前
有序数组中的单一元素(一)
数据结构·算法
jiunian_cn20 小时前
【C++11】C++11重要新特性详解
开发语言·c++
颜酱20 小时前
从经典问题入手,吃透动态规划核心(DP五部曲实战)
前端·javascript·算法
tbRNA20 小时前
C/C++ 内存管理
c语言·c++
WBluuue21 小时前
AtCoder Beginner Contest 438(ABCDEF)
c++·算法
k***921621 小时前
【c++】多态
java·开发语言·c++