21杭电多校10D - Pty hate prime numbers (容斥,预处理一半)

题目

给定\(n\)\(k\),求小于\(n\)且不被前\(k\)个素数整除的个数,\(n\le 10^{18},k\le 16\)

题解

很容易想到用容斥来解决,但\(T\cdot 2^{16}\)太大,得想办法经可能预处理。

前8个素数乘积很小数量级为\(10^7\),可以预处理前\(10^7\)的答案,从而可以O(1)查询容易数用前8个素数容斥的结果,然后再把剩下\(k-8\)个素数容斥出来,统计答案。

即预处理一半,类似dfs的双向搜索,可以将复杂度的指数部分减半。

可以想到预处理的方法,但没想到怎么用。。。

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
const int M = 1e7 + 10;
const int INF = 0x3f3f3f3f;
typedef long long ll;
#define endl '\n'
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
ll pri[N];
ll m, n;
int cnt;
bool isnp[N];
bool del[M];
ll pre[M];

void init() {
    isnp[1] = 1;
    for(int i = 2; i < N; i++) {
        if(!isnp[i]) {
            pri[cnt++] = i;
            for(int j = 2 * i; j < N; j += i)
                isnp[j] = 1;
        }
    }
}

ll d[N];
int sgn[N];

int main() {
    IOS;
    init();
    for(int i = 0; i < (1 << 16); i++) {
        ll res = 1;
        sgn[i] = 1;
        for(int j = 0; j < 16; j++) {
            if(i & (1 << j)) {
                res = res * pri[j];
                sgn[i] = -sgn[i];
            }
        }
        d[i] = res;
    }
    ll tot = 1;
    for(int i = 0; i < 8; i++) {
        int p = pri[i];
        tot = tot * p;
        for(int j = p; j < M; j += p) del[j] = 1;
    }
    for(int i = 1; i < M; i++) {
        pre[i] = pre[i - 1] + !del[i];
    }
    int t;
    cin >> t;
    while(t--) {
        ll n;
        int k;
        ll ans = 0;
        cin >> n >> k;
        if(k <= 8) {
            for(int i = 0; i < (1 << k); i++) {
                ans += n / d[i] * sgn[i];
            }
            cout << ans << endl;
        } else {
            ll ret = 0;
            for(int i = 1; i < (1 << (k - 8)); i++) {
                ll tn = n / d[i << 8];
                ret += (tn / tot * pre[tot] + pre[tn % tot]) * (-sgn[i << 8]);
            }
            ans = n / tot * pre[tot] + pre[n % tot] - ret;
            cout << ans << endl;
        }
    }
}
posted @ 2021-08-22 14:56  limil  阅读(67)  评论(0编辑  收藏  举报