📅  最后修改于: 2023-12-03 14:45:00.025000             🧑  作者: Mango
平滑数是指每个数字的质因数的指数都不大于$p$的数。比如$p=2$时,平滑数就是每个数字的因子都只包含2,3,5,7这样的素数,例如30($2\times 3\times 5$)和42($2\times 3\times 7$)都是平滑数。平滑数在因式分解、素数筛选等算法中有很重要的应用。
本题的任务是,在给定的范围$[a,b]$内找到所有的平滑数。
直接暴力枚举每个数字的因子并判断是否平滑,显然是行不通的。我们可以考虑预处理出所有的平滑数,并利用二分查找在给定的范围内快速找到所有的平滑数。
具体地,我们可以从小到大依次枚举$p$,每次求出所有因子中最大的素数因子不超过$p$的数,然后将这些数按顺序存放在一个数组中。这个数组就是所有小于等于$max$并且最大素数因子不超过$p$的平滑数。由于不同的$p$会产生不同的平滑数,我们需要对每个$[a,b]$区间都重新搜索一遍平滑数,然后二分查找即可。
具体实现时,可以使用一个二维数组$s$,其中$s[i][j]$表示以素数$j$为最大素数因子,第$i$个平滑数的值是多少。具体实现细节见下面的代码。
下面是参考实现(使用C++编写,时间复杂度为$O(max\log\log max + (b-a)\log^2 max)$):
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 1e7+10;
const int maxp = 10;
const int maxk = 1e7;
int p[maxp] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29};
int s[maxn][maxp], w[maxk], cnt[maxp], m;
void build_smooth_number() {
w[1] = 1;
for (int i = 0; i < maxp; i++) cnt[i] = 1; // 各个质数的指数
m = 1; // 平滑数总个数
for (int i = 0; i < maxp; i++) {
int prime = p[i];
while (prime <= maxk) {
cnt[i]++;
if (cnt[i] > 1 && w[m-1] != prime) w[m++] = prime; // 避免重复
prime *= p[i];
}
}
// 对于每个平滑数,计算它按照各个素数最大指数限制下的值
for (int j = 0; j < maxp; j++)
s[0][j] = 1; // 0是平滑数,各个素数的最大指数均为0
for (int i = 1; i < m; i++) {
int x = w[i];
memcpy(s[i], s[i-1], sizeof(s[0])); // 继承上一个平滑数的结果
for (int j = 0; j < maxp && p[j] <= x; j++) {
for (int k = 1; k < cnt[j]; k++) { // 处理p[j]^k的情况
int y = x / p[j]; // y是不包含p[j]的部分
for (int t = maxp-1; t >= 0; t--) {
if (t == j) s[i][t] *= p[j];
else if (t < j) s[i][t] *= y;
if (s[i][t] > maxk) s[i][t] = maxk+1; // 标记过大的数
}
x = y;
}
}
}
}
int main() {
build_smooth_number();
int T;
scanf("%d", &T);
while (T--) {
int a, b;
scanf("%d%d", &a, &b);
int L = lower_bound(w, w+m, a) - w; // a的下标
int R = upper_bound(w, w+m, b) - w - 1; // 注意这里要-1
if (L > R) puts("0");
else {
int ans = 0;
for (int i = L; i <= R; i++) {
int j;
for (j = 0; j < maxp && s[i][j] <= b; j++) {
if (s[i][j] >= a) break; // 找到第一个在[a,b]内的位置
}
if (j == maxp || s[i][j] > b || s[i][j] > maxk) continue; // 超出区间或者过大
int ok = 1;
for (int k = 0; k < j; k++)
if (s[i][k] <= b && s[i][k] > 1) { ok = 0; break; } // 不能包含比j更大的素数因子
if (ok) ans++;
}
printf("%d\n", ans);
}
}
return 0;
}
以上参考文献均提供了不同的算法思路和实现方法,对本题有很大帮助。