【定理简介】
- 筛是一种能够求解积性函数 的前缀和 的筛法,其前提条件为 可以由 简单表示,或能够直接快速计算,其中 为一个较小的常数。
- 其时间复杂度为 ,空间复杂度为 。
- 在求解过程中,我们还可以顺带求出对于每一个 , 的值。在一些题目中,上述功能可以直接帮助我们解决问题。
【算法流程】
首先,我们先来解决对于每一个 ,求解 的值。
我们先通过线性筛得到 以内所有的质数 以及它们的前缀 次方和 。
定义 ,其中 表示 最小的质因数。
直观地来说, 表示的就是 以内的在埃拉特斯特尼筛算法进行第 轮后尚未被筛去的数的 次方和。
一个合数 一定存在一个 以内的质因数,因此 即为所求,其中 为 以内的质数个数。
考虑如何通过 求出 。
若 ,那么埃拉特斯特尼筛算法的第 轮将不会筛去任何数,有 。
若 ,考虑埃拉特斯特尼筛算法的第 轮筛去的数从 中删除,有
。
这里由于 ,有 ,因此 以内最小质因数大于等于 的数的 次方之和即为 。
总结起来即为
这部分的时间复杂度为 。
接下来,我们考虑如何用上述信息求解 。
定义 ,即所有满足最小质因子大于等于 的 值之和。
由定义,最终答案 。
经过上面的计算,我们已经可以快速计算 ,因此答案中质数的贡献能够被轻松计算: 。
接下来考虑答案中合数的贡献,我们枚举这个合数的最小质因子及其出现次数,由于 为积性函数,我们可以得到合数的贡献为
总结起来即为
这部分计算即使不进行记忆化,也十分迅速,其复杂度被证明同样为 。
【代码】
有了上面的推导过程,我们只需要能够快速计算 即可解决问题。
我们发现,对于质数
因此当 ,即 时,
实际上计算了 内质数的和减去质数的个数,可以通过上述方式预处理得到。
当 ,即 , 被当做 计算,因此加上 即可。
时间复杂度 。
#include<bits/stdc++.h> using namespace std; const int MAXN = 2e5 + 5; const int P = 1e9 + 7; const int inv2 = 5e8 + 4; template <typename T> void chkmax(T &x, T y) {x = max(x, y); } template <typename T> void chkmin(T &x, T y) {x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } long long n, limit, m, val[MAXN]; int tot, prime[MAXN], Min[MAXN], pre[MAXN]; int cnt[MAXN], sum[MAXN], home1[MAXN], home2[MAXN]; int s(long long x, int y) { if (x <= 1 || prime[y] > x) return 0; int pos = (x <= limit) ? home1[x] : home2[n / x]; int ans = (sum[pos] - cnt[pos] + P) % P; ans = (ans - (pre[y - 1] - y + 1 + P) % P + P) % P; if (y == 1) ans = (ans + 2) % P; for (int i = y; i <= tot && 1ll * prime[i] * prime[i] <= x; i++) { long long now = prime[i], nxt = 1ll * prime[i] * prime[i]; for (int j = 1; nxt <= x; j++, now *= prime[i], nxt *= prime[i]) ans = (ans + 1ll * s(x / now, i + 1) * (prime[i] ^ j) + (prime[i] ^ (j + 1))) % P; } return ans; } void init(int n) { for (int i = 2; i <= n; i++) { if (Min[i] == 0) { Min[i] = i; prime[++tot] = i; pre[tot] = (pre[tot - 1] + i) % P; } for (int j = 1; j <= tot && prime[j] <= Min[i]; j++) { int tmp = prime[j] * i; if (tmp > n) break; Min[tmp] = prime[j]; } } } int main() { read(n), limit = sqrt(n); init(limit); for (long long i = 1, nxt; i <= n; i = nxt) { long long tmp = n / i; nxt = n / tmp + 1; val[++m] = tmp; cnt[m] = (val[m] - 1) % P; sum[m] = (val[m] + 2) % P * ((val[m] - 1) % P) % P * inv2 % P; if (sum[m] < 0) sum[m] += P; if (tmp <= limit) home1[tmp] = m; else home2[i] = m; } for (int j = 1; j <= tot; j++) for (int i = 1; 1ll * prime[j] * prime[j] <= val[i]; i++) { long long tmp = val[i] / prime[j]; if (tmp <= limit) cnt[i] -= cnt[home1[tmp]] - (j - 1); else cnt[i] -= cnt[home2[n / tmp]] - (j - 1); cnt[i] = (cnt[i] % P + P) % P; if (tmp <= limit) sum[i] -= 1ll * prime[j] * (sum[home1[tmp]] - pre[j - 1]) % P; else sum[i] -= 1ll * prime[j] * (sum[home2[n / tmp]] - pre[j - 1]) % P; sum[i] = (sum[i] % P + P) % P; } writeln((1 + s(n, 1)) % P); return 0; }