一、先看原题

二、题目解析:
🏰《相等序列》------数字王国的"统一魔法"✨
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)
说明:
i是x的一块积木!
③ 数一数有多少块这种积木
cpp
while(x%i==0){
x/=i;
cnt++;
}
👉 比如:
cpp
12 = 2 × 2 × 3
那:
cpp
cnt = 2(两个 2)
④ 记到统计表里
cpp
num[i][cnt]++;
👉 表示:
有一个数字
👉 用了
cnt个i
⑤ 最后剩下的大质数
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]个数字 -
它们的质数
i有j个 -
目标是
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、这道题教会学生的"通用能力"🚀
真正学完这道题,学生其实学会了:
-
不怕复杂问题
-
会"拆问题"
-
会找"最省力的方法"
-
明白:
👉 最优解不是乱试出来的,是按照步骤一步一步统计出来的