求 $\pi(n)$,$n \le 10^8$。
对于每一个质数进行一轮筛。
时间复杂度 $O(n \ln \ln n)$。
const int N = 1e8 + 5;
int n; bitset<N> ip;
void solve() {
cin >> n;
ip[1] = 1;
FOR(i, 2, n) if(!ip[i])
for(int j = i << 1; j <= n; j += i)
ip[j] = 1;
cout << n - ip.count() << endl;
}
求 $\pi(n)$,$n \le 10^8$。
将每个数筛出其最小的质因数。
时间复杂度 $O(n)$。
const int N = 1e8 + 5;
int n, p[N / 10], cnt;
bitset<N> ip;
void solve() {
cin >> n;
ip[1] = 1;
FOR(i, 2, n) {
if(!ip[i]) {
p[++cnt] = i;
}
for(int j = 1; j <= cnt && p[j] * i <= n; j++) {
ip[p[j] * i] = 1;
if(i % p[j] == 0) break;
}
}
cout << cnt << endl;
}
由于线性筛是筛出其最小的质因数,所以只要 $f(p^k)$ 能快速求出,线性筛就可以在 $O(n)$ 筛出任何积性函数。
筛 $\varphi(n)$ 和 $\mu(n)$,$n \le 10^7$。
只要知道 $f(p^k)$ 即可:
$\varphi(p)=p-1$
$\varphi(p^k)=(p-1)p^{k-1}=\varphi(p^{k-1})p~(k>1)$
$\mu(p)=-1$
$\mu(p^k)=0~(k>1)$
时间复杂度 $O(n)$。
const int N = 1e7 + 5;
int n, p[N], ip[N], phi[N], mu[N], cnt;
void solve() {
cin >> n;
mu[1] = phi[1] = ip[1] = 1;
FOR(i, 2, n) {
if(!ip[i]) {
p[++cnt] = i;
phi[i] = i - 1;
mu[i] = -1;
}
for(int j = 1; j <= cnt && p[j] * i <= n; j++) {
ip[p[j] * i] = 1;
if(i % p[j] == 0) {
phi[p[j] * i] = phi[i] * p[j];
break;
}
else {
phi[p[j] * i] = phi[i] * (p[j] - 1);
mu[p[j] * i] = - mu[i];
}
}
}
FOR(i, 1, n) cout << i << " " << phi[i] << " " << mu[i] << endl;
}
求 $\sum_{i=1}^{n}\varphi(i)$ 和 $\sum_{i=1}^{n}\mu(i)$,$n \le 10^{10}$
首先考虑积性函数 $f(n)$ 的前缀和函数 $s(n)$,构造函数 $g(n)$,考虑 $(f \ast g)$ 的前缀和:
$\sum_{i=1}^{n}(f \ast g)(i)$
$=\sum_{i=1}^{n}\sum_{d|i}g(d)f(\frac{i}{d})$
$=\sum_{i=1}^{n}g(i)\sum_{ij\le n}f(j)$
$=\sum_{i=1}^{n}g(i)s(\left \lfloor \frac{n}{i} \right \rfloor )$
把 $i=1$ 差出来:
$g(1)s(n)$
$=\sum_{i=1}^{n}g(i)s(\left \lfloor \frac{n}{i} \right \rfloor )-\sum_{i=2}^{n}g(i)s(\left \lfloor \frac{n}{i} \right \rfloor )$
$=\sum_{i=1}^{n}(f \ast g)(i)-\sum_{i=2}^{n}g(i)s(\left \lfloor \frac{n}{i} \right \rfloor )$
对后面的式子进行整除分块和记忆化即可,可以做到 $O(n^\frac{3}{4})$。
若线性筛预处理到 $n^\frac{2}{3}$,则可以做到 $O(n^\frac{2}{3})$。
先构造 $g(n)$。因为 $I \ast \varphi=id$ 和 $I \ast \mu=\epsilon $,所以直接构造 $g=I$。
时间复杂度 $O(n^\frac{2}{3})$。
const int N = 5e6 + 5;
int p[N], ip[N], phi[N], mu[N], cnt;
ll sphi[N]; int smu[N];
map<ll, ll> mphi;
map<ll, int> mmu;
void init() {
mu[1] = phi[1] = ip[1] = 1;
FOR(i, 2, N - 1) {
if(!ip[i]) {
p[++cnt] = i;
phi[i] = i - 1;
mu[i] = -1;
}
for(int j = 1; j <= cnt && p[j] * i < N; j++) {
ip[p[j] * i] = 1;
if(i % p[j] == 0) {
phi[p[j] * i] = phi[i] * p[j];
break;
}
else {
phi[p[j] * i] = phi[i] * (p[j] - 1);
mu[p[j] * i] = - mu[i];
}
}
}
FOR(i, 1, N - 1) sphi[i] = sphi[i - 1] + phi[i];
FOR(i, 1, N - 1) smu[i] = smu[i - 1] + mu[i];
}
ll get_phi(ll n) {
if(n < N) return sphi[n];
if(mphi.count(n)) return mphi[n];
ll res = n * (n + 1) / 2;
for(ll l = 2, r; l <= n; l = r + 1) {
r = n / (n / l);
res -= (r - l + 1) * get_phi(n / l);
}
return mphi[n] = res;
}
int get_mu(ll n) {
if(n < N) return smu[n];
if(mmu.count(n)) return mmu[n];
int res = 1;
for(ll l = 2, r; l <= n; l = r + 1) {
r = n / (n / l);
res -= (r - l + 1) * get_mu(n / l);
}
return mmu[n] = res;
}
void solve() {
ll n; cin >> n;
cout << get_phi(n) << " " << get_mu(n) << endl;
}
求 $\pi(n)$,$n \le 10^{13}$。
一道 Meissel-Lehmer 算法的模板题洛谷题解里没有 Meissel-Lehmer,大悲啊。
这里介绍 Meissel-Lehmer 算法。
先线性筛算出前大于等于 $n^\frac{1}{2}$ 个数内的素数和 $\pi(n)$。
然后对于求 $\pi(n)$,考虑对其产生贡献的素数分治。 考虑对于 $n^\frac{1}{3}$ 分治,先算出 $(n^\frac{1}{3},n]$ 中对于 $n^\frac{1}{3}$ 以内的素数筛不掉的数,暂定一个 dp,$dp_{i,j}$ 表示 $[1,i]$ 中不会被前 $j$ 个素数筛掉的个数。
接下来不难发现,现在筛剩下的 $(n^\frac{1}{3},n]$ 中的数,如果是合数,则其必定为两个大于 $n^\frac{1}{3}$ 的素数的乘积,其他即为素数。
计算这类由两个素数相乘的数的个数很简单,直接考虑枚举小的素数,去计算另外一个素数的方案数即可,假设当前选取了小素数 $p$,则其筛掉的数就是 $[p,\frac{n}{p}]$ 中的素数的个数,即为 $\pi(\frac{n}{p})-\pi(p)+1$。
结合一下上述,可得出 $\pi(n)$ 的递归公式:
$$
\pi(n)=dp_{n,\pi(n^\frac{1}{3})}-1+\pi(n^\frac{1}{3})
-\sum_{p\in(n^\frac{1}{3},n^\frac{1}{2}]} \pi(\frac{n}{p})-\pi(p)+1
$$
公式前面减一是因为 $1$ 是筛不掉的,其中 $p$ 为素数。
然后考虑如何求 dp 数组,考虑转移。考虑计算最后一个素数的贡献,发现其贡献是其倍数中没有被前面素数筛掉的数的个数,发现可以再利用 dp 数组,所以转移为 $dp_{i,j}=dp_{i,j-1}-dp_{\frac{i}{p_j},j-1}$,其中 $p_j$ 表示第 $j$ 个素数。
考虑边界情况,显然的是 $dp_{i,0}=i$。但这样还不够优,考虑 $p_j^2\ge i$ 的情况,这时 $[1,i]$ 中的合数全被筛过,只剩下素数,所以值即为 $\max(0,\pi(i)-j+1)$,加一是为了留下 $1$,与前面同步。
这样一个基础的 Meissel-Lehmer 算法原理讲解就完成了。
接下来是实现,对于 dp 数组,可以预处理出一部分,例如 $i\le 1.8 \times 10^6,j\le 60$ 的情况。然后由于这道题空间有限,前面公式里的 $\pi(\frac{n}{p})$ 会超出预处理的范围,这是要继续递归。
时间复杂度应该在 $O(n^\frac{2}{3})$ 级别,足以通过。
const int N = 8e6 + 10;
const int MI = 1.8e6;
const int MJ = 60;
ll n;
int f[MI + 5][MJ + 5];
int p[N / 10], ip[N], g[N], cnt;
void init() {
FOR(i, 2, N - 1) {
if(!ip[i]) {
p[++cnt] = i;
}
for(int j = 1; j <= cnt && p[j] * i < N; j++) {
ip[i * p[j]] = 1;
if(i % p[j] == 0) {
break;
}
}
}
FOR(i, 1, N - 1) g[i] = g[i - 1] + !ip[i];
FOR(i, 1, MI) f[i][0] = i;
FOR(i, 1, MI) FOR(j, 1, MJ)
f[i][j] = f[i][j - 1] - f[i / p[j]][j - 1];
}
ll dp(ll i, ll j) {
if(i <= MI && j <= MJ) return f[i][j];
if(!i || !j) return i;
if(i < N && 1ll * p[j] * p[j] >= i) return max(0ll, g[i] - j);
return dp(i, j - 1) - dp(i / p[j], j - 1);
}
ll pi(ll n) {
if(n < N) return g[n] - 1;
ll sn = pow(n, 1. / 3);
ll m = g[sn] - 1;
ll res = dp(n, m) + m - 1;
for(m++; 1ll * p[m] * p[m] <= n; m++)
res -= pi(n / p[m]) - pi(p[m]) + 1;
return res;
}
定义积性函数 $f(x)$,且 $f(p ^ k) = p ^ k(p ^ k - 1)$($p$ 是一个质数),求 $$\sum_{i = 1} ^ n f(i)$$ 对 $10 ^ 9 + 7$ 取模。 $n \le 10^{10}$ 。
考虑拆解质因数,设 $n$ 的最小质因子为 $mp(n)$,
设 $S(n,j)$ = $\sum_{i=2}^{n}f(i)[mp(i)>j]$。
然后考虑转移,转移考虑质数和合数处的值。
设 $f(i)$ 质数处的前缀和为 $q(n)=\sum_{i=2}^{n}f(i)[i \in p]$。
那么质数处的和即为 $q(n)-\sum_{i=1}^{j}f(p_i)$。
然后对于合数部分,拆出最小质因数以及其次数,得出:
$\sum_{k>j}\sum_{c\le 1}f(p_k^c)(S(\frac{n}{p_k^c}, k)+[c>1])$
然后考虑 $q(n)$ 怎么求,为了删掉合数的贡献,考虑设 $g(n,j)=\sum_{i=2}^{n}f(i)[i \in p \mid mp(i)>j]$。
考虑 $g(n,j-1)$ 到 $g(n,j)$ 的转移。
当 $p_j^2>n$ 时,没有符合条件的合数,直接转移。
否则删去质因子含有 $p_j$ 的合数的贡献:
$g(n,j)=g(n,j-1)-f(p_j)(S(\frac{n}{p_j},j-1)-\sum_{i=1}^{j-1}f(p_i))$
不难发现这个转移中的 $f$ 只需要用到质数处的值,所以可以直接用一个完全积性函数替换掉。求 $g(n,0)$ 需要快速求出这个完全积性函数的前缀和,如果不好算可以进行多项式拆解,例如例题中的 $f(p)=p(p-1)=p^2-p$,分别计算 $f(p)=p^2$ 和 $f(p)=p$ 的值,然后减一减即可。
然后也不难发现 $g(i,j)$ 有用的 $i$ 就是所有 $\left \lfloor \frac{n}{k} \right \rfloor $,一共只有 $O(\sqrt{n})$ 种。
那么 $q(n)=g(n,|p|)$,答案等于 $S(n,0)+f(1)$。
时间复杂度 $O(\frac{n^\frac{3}{4}}{\log n})$。
const int N = 2e5 + 5;
const int P = 1e9 + 7;
inline int add(int x, int y) { return (x + y < P ? x + y : x + y - P); }
inline void Add(int &x, int y) { x = (x + y < P ? x + y : x + y - P); }
inline int sub(int x, int y) { return (x < y ? x - y + P : x - y); }
inline void Sub(int &x, int y) { x = (x < y ? x - y + P : x - y); }
inline int mul(int x, int y) { return (1ll * x * y) % P; }
inline void Mul(int &x, int y) { x = (1ll * x * y) % P; }
ll n; int sn, m;
int ip[N], p[N], cnt;
int s1[N], s2[N], g1[N], g2[N], id1[N], id2[N];
ll w[N];
void init() {
ip[1] = 1;
FOR(i, 2, sn) {
if(!ip[i]) {
p[++cnt] = i;
}
for(int j = 1; j <= cnt && p[j] * i <= sn; j++) {
ip[p[j] * i] = 1;
if(i % p[j] == 0) break;
}
}
FOR(i, 1, cnt) {
s1[i] = add(s1[i - 1], p[i]);
s2[i] = add(s2[i - 1], mul(p[i], p[i]));
}
}
int f1(int n) {
return mul(mul(n, n + 1), (P + 1) / 2);
}
int f2(int n) {
return mul(mul(mul(n, n + 1), n * 2 + 1), (P + 1) / 6);
}
int get(ll x) {
if(x <= sn) return id1[x];
return id2[n / x];
}
int S(ll n, int j) {
if(p[j] > n) return 0;
int res = sub(sub(g2[get(n)], g1[get(n)]), sub(s2[j], s1[j]));
for(int i = j + 1; i <= cnt && 1ll * p[i] * p[i] <= n; i++)
for(ll c = 1, v = p[i]; v <= n; v *= p[i], c++)
Add(res, mul(mul(v % P, v % P - 1), S(n / v, i) + (c > 1)));
return res;
}
void solve() {
cin >> n; sn = sqrt(n); init();
for(ll l = 1, r; l <= n; l = r + 1) {
r = n / (n / l);
w[++m] = n / l;
g1[m] = f1(w[m] % P) - 1;
g2[m] = f2(w[m] % P) - 1;
if(w[m] <= sn) id1[w[m]] = m;
else id2[n / w[m]] = m;
}
FOR(i, 1, cnt) {
for(int j = 1; j <= m && 1ll * p[i] * p[i] <= w[j]; j++) {
Sub(g1[j], mul(p[i], sub(g1[get(w[j] / p[i])], s1[i - 1])));
Sub(g2[j], mul(mul(p[i], p[i]), sub(g2[get(w[j] / p[i])], s2[i - 1])));
}
}
cout << add(S(n, 0), 1) << endl;
}