📜  P –在给定范围内的平滑数(1)

📅  最后修改于: 2023-12-03 14:45:00.025000             🧑  作者: Mango

P –在给定范围内的平滑数

平滑数是指每个数字的质因数的指数都不大于$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;
}
参考文献

以上参考文献均提供了不同的算法思路和实现方法,对本题有很大帮助。