埃筛做法

首先什么是埃筛:

#include <stdio.h>
#include <string.h>
const int N = 100 + 8;
int isPrime[N];
void sieve() {
    memset(isPrime, -1, sizeof isPrime);
    isPrime[0] = isPrime[1] = 0;
    for (int i = 2; i < N; ++i)
        if (isPrime[i])
            for (int j = 2; i * j < N; ++j) isPrime[i * j] = 0;
}
int main() {
    sieve();
    for (int i = 2; i < N; ++i)
        if (isPrime[i]) printf("%d ", i);
    return 0;
}

可以发现,在朴素的埃筛里,对于每一个合数,它会被每个它的质因数筛到。而如果是质数,他的质因数合并就是它本身。

所以只需要实时更新下每个数的质因数合并即可。

#include <bits/stdc++.h>
#define sc(x) scanf("%lld", &(x))
#define pr(x) printf("%lld\n", (x))
#define rep(i, l, r) for (int i = l; i <= r; ++i)
using namespace std;
typedef long long ll;
const int N = 4e6 + 7;
const int mod = 1e9 + 7;
bitset<N> isPrime;
ll ans[N];
int main() {
    ll w[10] = {1};
    rep(i, 1, 9) w[i] = w[i - 1] * 10;
    ll n;
    sc(n);
    isPrime.set();
    rep(i, 2, n) {
        if (isPrime[i]) {
            ans[i] = i;
            int len = log10(i) + 1;
            for (int j = 2; i * j <= n; ++j) {
                int now = i * j;
                isPrime[now] = 0;
                do {
                    ans[i * j] = (ans[i * j] * w[len] % mod + i) % mod;
                    now /= i;
                } while (now % i == 0);
            }
        }
    }
    ll res = 0;
    rep(i, 2, n) res = (res + ans[i]) % mod;
    pr(res);
    return 0;
}

这种做法时间复杂度本题300ms

欧拉筛 + DFS

欧拉筛是近似线性的筛法,这意味着不能像埃筛一样,对每个数更新答案。

但是可以从另一个角度出发:

  • 对于每个质数,它的质因数合并就是它本身。
  • 对于每个合数,它的质因数合并是它所有质因数的拼接。
  • 反过来说,可以用所有的质因数乘出来所有的数,对于质因数合并,也就是拼接。
  • 所以每个数和它的质因数合并都是一一对应的。

所以,可以直接去尝试拼接就可以了,如果是在所求范围内的,就加上权重即可,这也是唯一分解定理的逆向应用,非常巧妙。

#include <bits/stdc++.h>
#define sc(x) scanf("%lld", &(x))
#define pr(x) printf("%lld\n", (x))
#define rep(i, l, r) for (int i = l; i <= r; ++i)
using namespace std;
typedef long long ll;
const int N = 4e6 + 7;
const int mod = 1e9 + 7;
bitset<N> isPrime;
int prim[N + 10];
int pn = 0;
ll ans, n;
int w[10] = {1};
int pw[N];
void pre(int N) {
    rep(i, 1, 9) w[i] = w[i - 1] * 10;
    isPrime.set();
    for (int i = 2; i <= N; i++) {
        if (isPrime[i]) pw[pn] = w[int(log10(i) + 1)], prim[pn++] = i;
        for (int j = 0; j < pn && i * prim[j] <= N; ++j) {
            isPrime[i * prim[j]] = 0;
            if (i % prim[j] == 0) break;
        }
    }
}
void dfs(int pos, ll key, ll val) {
    ans = (ans + val) % mod;
    for (int i = pos; i < pn; ++i) { // merge with bigger prime num
        if (key * prim[i] > n) return;
        dfs(i, key * prim[i], (val * pw[i] % mod + prim[i]) % mod);
    }
}
int main() {
    sc(n);
    pre(n);
    for (int i = 0; i < pn && prim[i] <= n; ++i)
        // start dfs from each prime num
        dfs(i, prim[i], prim[i]);
    pr(ans);
    return 0;
}

这种做法时间复杂度本题70ms