2021杭电多校10 D.Pty hates prime numbers题解

前言

组队赛选的这场,遗憾爆0,被对面队打爆,赛后狠狠补题。这道题的题解,以及网上搜到的其他题解看了好久没看懂,在问了队里大腿多次后,总算磨出来了,这里讲一下我的理解。

题意

多次询问,每次给定 n n n k k k, 如果一个数的质因数里包括前 k k k 个质数,则这个数是 讨厌 的,问 1 1 1 n n n 中有多少个数是不讨厌的。
数据范围 1 ≤ n ≤ 1 0 18 , 1 ≤ k ≤ 16 1 \leq n \leq 10^{18}, 1 \leq k \leq 16 1n1018,1k16

思路

首先直接 2 k 2^k 2k 的枚举质因数,然后容斥,这个是简单的。具体来说就是直接dfs 爆搜,枚举每个质因数选或者不选,选的话就乘上这个数,最终得到一个 x x x 以及 容斥系数 f f f ,容斥系数根据选的质因数的奇偶性,决定是 1 1 1 或者 − 1 -1 1。然后将答案加上 1 − n 1 - n 1n 中是 x x x 的倍数的个数乘 f f f

通过这个容斥, 我们能得到的是 1 - n 中不是前k个质数倍数的个数,这句话,以及题解里面也是类似的描述,有一点歧义的地方。不是前k个质数的倍数,意思是质因数不包含前 k 个质数。而不是,不是前k个质数的乘积的倍数(因为题解里面多次强调了前8个质数的乘积9699690,就容易误解不是前8个质数倍数,指不是9699690倍数)。

然后因为 k k k 的数据范围是16,所以直接这样 2 k 2^k 2k 容斥枚举是T的,因为多测次数很多。所以正解的思路是,先对前8个质数进行容斥枚举,预处理出一个数组 s u m [ i ] sum[i] sum[i] 表示 1 1 1 i i i 中,质因数不包含前8个质数的数字个数。然后只对第 8 8 8 到第 k k k 个质数进行枚举容斥。

就是这个什么叫只对后面几个数进行容斥,把我控了很久,因为普通的容斥,肯定是考虑前面和后面,总的选的数的个数,决定正负啥的。

然后我是这样去理解:
对一些位去容斥,最终目的是为了求出,1 - n 中不包含这些质因数的数字个数。然后我们对 9 到 k位去容斥,所以保证了这些数一定不包含9 到 k个质因数,然后我们只需要再保证这些数不包含前8个质因数,那这些数就是符合题意的。换言之,就是在容斥后几位的同时,保证能忽略掉前8位的影响。

然后具体的,由于容斥过程中,我们枚举了哪些质因数得到乘积 x x x 之后,我们要求的反而是 1 − n 1 - n 1n 中是 x x x 的倍数的个数。

因为是对后几个质因数容斥,所以我们要求的是 1 − n 1-n 1n 中是枚举到的后几个质因数的倍数的个数,因为忽略了前8位,所以我们还得保证求出来的这些数不能有前 8个质因数。也就是,通过容斥计算 1 − n 1-n 1n 不含后几位质因数的数量,在整个容斥过程中,还保证了全程枚举到的数不包含前 8 8 8 个质因数,所以最终算出来的就是前 k k k 个质因数都不包含的总数。

在后几位的容斥过程中,假如枚举到的乘积是 x x x ,问题转化为求 1 − n 1-n 1n 中是 x x x 的倍数,并且不包含前 8 8 8 个质因数的数字个数。
由于是 x x x 倍数,所以这些数 x , 2 x , 3 x … x, 2x, 3x \dots x,2x,3x 一定能表示成 k x ( 1 ≤ k ≤ ⌊ n k ⌋ ) kx (1 \leq k \leq \lfloor \frac{n}{k} \rfloor) kx(1kkn⌋) ,因为 x x x 是后几个质因数倍数,所以肯定不含前 8 8 8 个质因子。所以我们相当于,求 1 − n k 1 - \frac{n}{k} 1kn 中,有多少个数不含前 8 8 8 个质因数,这个问题等价于求 n 1 = n k , k 1 = 8 n_1 = \frac{n}{k}, k_1 = 8 n1=kn,k1=8 的子问题了。所以可以预处理 k = 8 k=8 k=8 的所有答案。

然后由于 n n n 很大,没办法预处理 n = 1 − 1 0 18 , k = 8 n = 1 - 10^{18}, k = 8 n=11018,k=8 的所有答案,这时候还需要发现一个性质。 由于前8个质因数的乘积,或者说是最小公倍数是9699690, 假设有一个比较大的 n n n, 画在数轴上,把有前 8 8 8 个质因数的点 i i i 标1,那么显然这些数的 l c m lcm lcm 就是一个循环节,所以 这个很大的 n n n 的答案等于拥有这些循环节的个数乘上一个循环节的答案,加上多出来那一截 n m o d    l c m n \mod lcm nmodlcm 对应的答案。(从这个循环节和数轴的角度,感觉比较好理解题解里面那个为什么要取余 lcm)

所以得到题解里面那个式子,这里的答案指的是 k = 8 k = 8 k=8 时, 1 − n 1-n 1n 中不含前 8 8 8 个质因数的数字个数。
题解式子
然后就是代码了

#include <bits/stdc++.h>
#define int long long
#define inf 0x3f3f3f3f
#define ll long long
#define pii pair<int, int>
#define db double
#define all(a) a.begin(), a.end()
using namespace std;
const int maxn = 1e7 + 10;
const int mod = 998244353;
int n, k;
int prime[17] = {0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53};
int ans, P = 1, sum[maxn], vis[maxn];
int cal(int x) {
    if (k <= 8)
        return n / x;  // 1 - n 中是前k个数的倍数的数量
    else {
        int y = n / x;
        return sum[P] * (y / P) + sum[y % P];
    }
}
void dfs(int num, int up, int now, int f) {
    if (num == up + 1) {
        ans += f * cal(now);
        return;
    }
    dfs(num + 1, up, now * prime[num], f * -1);
    dfs(num + 1, up, now, f);
}
void init() {
    for (int i = 1; i <= 8; i++)
        P *= prime[i];
    for (int i = 1; i <= 8; i++) {
        for (int j = prime[i]; j <= P; j += prime[i])
            vis[j] = 1;  // j中是前8个数的倍数
    }
    for (int i = 1; i <= P; i++) {
        sum[i] = sum[i - 1];
        if (vis[i] == 0)
            sum[i]++;  // 1 - i 中不是前8个数的倍数的个数
    }
}
void solve() {
    cin >> n >> k;
    ans = 0;
    if (k <= 8) {
        dfs(1, k, 1, 1);
        cout << ans << endl;
    } else {
        dfs(9, k, 1, 1);
        cout << ans << endl;
    }
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    init();
    int t;
    cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}
  • 11
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值