GESP五级真题:小杨的幸运数——不会这两步,99%的人会超时
核心考点:筛法思想 + 二分查找
这道题表面在问「幸运数」,实际考的是你能不能把「倍数的倍数」这个关系转化为预处理打表 + 快速查询的经典模型。读完这篇,你会发现它和埃氏筛如出一辙——即使你没学过埃氏筛,看完这篇你就学会了。
📋 原题原文
题目描述
小杨认为,所有大于等于 的完全平方数都是他的超级幸运数。
小杨还认为,所有超级幸运数的倍数都是他的幸运数。自然地,小杨的所有超级幸运数也都是幸运数。
对于一个非幸运数,小杨规定,可以将它一直 ,直到它变成一个幸运数。我们把这个过程叫做幸运化。例如,如果 ,那么 是最小的幸运数,而 不是,但我们可以连续对 做 次 操作,使其变为 ,所以 幸运化后的结果是 。
输入格式
第一行 个正整数 。
接下来 行,每行一个正整数 ,表示需要判断(幸运化)的数。
输出格式
输出 行,对于每个给定的 ,如果它是幸运数,请输出 lucky,否则请输出将其幸运化后的结果。
数据范围
• • •
题意概括
小杨定义了一套幸运数规则。给定正整数 ,所有 的完全平方数是超级幸运数,超级幸运数的倍数(含自身)是幸运数。
现在给出 和 个询问 ,对每个 :
• 若 是幸运数 → 输出 lucky• 否则,不断 直到变成幸运数 → 输出这个幸运化结果
概念解析
超级幸运数:
幸运数:
幸运化:非幸运数 直至遇到幸运数,产出该幸运数
样例速览
按照输入格式,第一行给出两个数:(超级幸运数的门槛)、(共有 个查询)。接下来 行是每个 的值:
lucky | |||
lucky |
关键观察: 为什么不走到 而是停在 ?因为 , 是超级幸运数,所以 也是幸运数。幸运化要找的是第一个 的幸运数,,所以停在 。
样例 2:输入第一行 16 11,即 ,接下来 行依次为 。
lucky |
思路分析
第一步:理解幸运数的结构
设 为第一个 的完全平方数,则:
• 是最小的超级幸运数,也是最小的幸运数 • 任何 的数一定不是幸运数 • 幸运数 =
第二步:用筛法预处理所有幸运数
核心思想:枚举所有超级幸运数 (),然后标记 的所有倍数。
这和埃氏筛(Eratosthenes)的过程一模一样——只是把「质数」换成了「超级幸运数」,把「筛掉合数」换成了「标记倍数」。
for (int i = 1; i * i <= MAXV; i++) {
int d = i * i; // 完全平方数
if (d < a) continue; // 不是超级幸运数
for (int j = 1; j * d <= MAXV; j++)
// 标记 d 的所有倍数为幸运数
vis[j * d] = true;
}第三步:二分查找快速回答查询
将所有幸运数收集到一个有序数组中。对每个查询 :
1. 找第一个 的幸运数 2. 若 → lucky3. 否则输出
为什么取 ?
因为当 时, 不可能 ≥ 任何超级幸运数,一定不是幸运数,结果取第一个幸运数即可(它一定 )。
复杂度预估
假设 , 最坏为 :
不到两百万次标记, 次二分查找各 ,总耗时远小于 1 秒。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int MAXV = 1002001 + 10; // 范围略大于 x 上限 (1e6),预留缓冲
int a, N, x;
vector<int> lucky; // 有序的幸运数列表
bool vis[MAXV]; // 幸运数标记数组
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> a >> N;
// 筛法:枚举完全平方数,标记其所有倍数为幸运数
for (int i = 1; i * i <= MAXV; i++) {
int d = i * i;
if (d < a) continue; // 不是超级幸运数,跳过
for (int j = 1; j * d <= MAXV; j++)
if (!vis[j * d]) {
vis[j * d] = true;
lucky.push_back(j * d); // 记录幸运数
}
}
// 排序后二分
sort(lucky.begin(), lucky.end());
while (N--) {
cin >> x;
// 第一个 ≥ max(a, x) 的幸运数
int p = lower_bound(lucky.begin(), lucky.end(), max(a, x))
- lucky.begin();
// 根据题意,lucky 中一定存在 ≥ max(a, x) 的值(MAXV 已预留缓冲)
if (lucky[p] == x)
cout << "lucky\n";
else
cout << lucky[p] << '\n';
}
return 0;
}复杂度分析
其中 ,(幸运数个数)。总内存约 5 MB(bool 数组 1 MB + vector 4 MB),运行时间完全可以接受。
边界情况
1. 且 是完全平方数
如 : 虽然是完全平方数,但 ,不是超级幸运数,也不是幸运数。幸运化结果为第一个 的幸运数——即 本身。2. 接近上限
如 :第一个超级幸运数是 ,下一个是 。若 不是任何超级幸运数的倍数,幸运化结果即为 。代码将范围设为 ,预留了足够缓冲。3.
是超级幸运数,而所有正整数都是 的倍数 → 所有数都是幸运数 → 全部输出lucky。4.
二分查找 完全胜任,无需额外优化。
总结
这道 GESP 五级真题的核心解题步骤只有两步:
1. 筛——枚举 的完全平方数,用倍数标记法生成所有幸运数 2. 查——排序后二分查找,快速回答每个查询
很多同学第一次做这道题时会试图对每个 模拟 操作,那样在 较大且 较大时就会超时。实际上,「倍数的倍数」这个结构天然适合预处理 + 查表——这和埃氏筛、素数打表的思路是完全一致的。
你觉得还有没有其他预处理方式?如果 很大、 很小,有没有更快的做法?欢迎留言讨论!
朱博士 | 信息学奥赛教练
GESP · CSP · NOIP · 盐城本地辅导
后台回复「咨询」→ 免费入门诊断