杜教筛
前置知识
杜教筛
求积性函数 \(f(n)\) 的前缀和,即 \(\sum\limits_{i = 1}^{n} f(i)\)。
考虑狄利克雷卷积能否帮助我们优化。
记 \(S(n) = \sum\limits_{i = 1}^{n}f(i)\),则有:
欲求 \(S(n)\),可令 \(d = 1\) 发现:
如果我们能找到一个函数 \(g(n)\) 使得 \((f * g)(n)\) 与 \(g(n)\) 的前缀和能够快速求出,那么 \(f(n)\) 的前缀和就可以通过数论分块快速求出。
ll get_sum(ll n)
{
if (n < N) return pref[n]; // 预处理的前缀和
if (sumf[n]) return sumf[n]; // 哈希存储前缀和
ll res = get_sum(n); // f * g 的前缀和
for (ll l = 2, r; l <= n; l = r + 1)
{
r = n / (n / l);
res -= get_g(l, r) * get_sum(n / l); // g(l ~ r) * f(n / l)
}
return sumf[n] = res;
}
\(\varphi(n)\) 的前缀和
根据 \(\varphi * {\mathbf{1}} = {\rm{Id}}\),容易得出以下结论:
用哈希记忆化一下,考虑递归求解,递归代码如下:
ll get_sumphi(ll n)
{
if (n < N) return phi[n];
if (sumphi[n]) return sumphi[n];
ll res = n * (n + 1) / 2; // f * g 的前缀和
for (ll l = 2, r; l <= n; l = r + 1)
{
r = n / (n / l);
res -= (r - l + 1) * get_sumphi(n / l); // g(l ~ r) * f(n / l)
}
return sumphi[n] = res;
}
\(\mu(n)\) 的前缀和
根据 \(\mu * {\mathbf{1}} = \varepsilon\),容易得出以下结论:
同样的,可以写出下面的代码:
ll get_summu(ll n)
{
if (n < N) return mu[n];
if (summu[n]) return summu[n];
ll res = 1;
for (ll l = 2, r; l <= n; l = r + 1)
{
r = n / (n / l);
res -= (r - l + 1) * get_summu(n / l);
}
return summu[n] = res;
}
题目实例
洛谷 P4213 【模版】杜教筛
给定一个正整数,求
对于全部的测试点,保证 \(1 \leq T \leq 10\),\(1 \leq n \lt 2^{31}\)。
也就是我们上面讨论的 \(\varphi(n)\) 与 \(\mu(n)\) 的前缀和。
参考代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e7 + 10;
int cnt, st[N], primes[N];
ll n, phi[N], mu[N];
unordered_map<ll, ll> sumphi, summu;
ll get_sumphi(ll n)
{
if (n < N) return phi[n];
if (sumphi[n]) return sumphi[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_sumphi(n / l);
}
return sumphi[n] = res;
}
ll get_summu(ll n)
{
if (n < N) return mu[n];
if (summu[n]) return summu[n];
ll res = 1;
for (ll l = 2, r; l <= n; l = r + 1)
{
r = n / (n / l);
res -= (r - l + 1) * get_summu(n / l);
}
return summu[n] = res;
}
void init()
{
phi[1] = mu[1] = 1;
for (ll i = 2; i < N; i ++ )
{
if (!st[i]) primes[cnt ++ ] = i, phi[i] = i - 1, mu[i] = -1;
for (ll j = 0; primes[j] * i < N && j < cnt; j ++ )
{
st[primes[j] * i] = 1;
if (i % primes[j] == 0)
{
phi[primes[j] * i] = phi[i] * primes[j];
break;
}
phi[primes[j] * i] = phi[i] * (primes[j] - 1);
mu[primes[j] * i] = -mu[i];
}
}
for (int i = 1; i < N; i ++ ) phi[i] += phi[i - 1], mu[i] += mu[i - 1];
}
void solve()
{
scanf("%lld", &n);
printf("%lld %lld\n", get_sumphi(n), get_summu(n));
}
int main()
{
int T;
init();
scanf("%d", &T);
while (T -- ) solve();
return 0;
}
洛谷 P3768 简单的数学题
对于所有的数据,\(n \leq 10^{10}\)。
对于所有的数据,\(5 \times 10^8 \leq p \leq 1.1 \times 10^9\) 且 \(p\) 为质数。
对 \(\gcd{(i, j)}\) 进行欧拉反演:
记 \(f(n) = n^2\varphi(n)\),\(g(n) = n^2\)。
记 \(S(n) = \sum\limits_{i=1}^{n}i^2\varphi(i)\),\(h(n) = \left(\sum\limits_{i=1}^n i\right)^2 = \frac{n^2(n+1)^2}{4}\),对后面这个式子进行数论分块,杜教筛求前面式子即可,同样的预处理出 \(S(n)\) 的前 \(n^{\frac{2}{3}}\) 项可以做到 \(O(n^{\frac{2}{3}})\)。
参考代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e7 + 10;
ll n, mod, inv_6, ans;
ll primes[N], st[N], cnt;
ll phi[N], pref[N];
unordered_map<ll, ll> sumf;
ll ksm(ll a, ll k)
{
ll res = 1;
while (k)
{
if (k & 1) res = res * a % mod;
k >>= 1;
a = a * a % mod;
}
return res;
}
void init()
{
phi[1] = 1;
for (ll i = 2; i < N; i ++ )
{
if (!st[i]) primes[cnt ++ ] = i, phi[i] = i - 1;
for (ll j = 0; primes[j] * i < N; j ++ )
{
st[primes[j] * i] = 1;
if (i % primes[j] == 0)
{
phi[primes[j] * i] = phi[i] * primes[j];
break;
}
phi[primes[j] * i] = phi[i] * (primes[j] - 1);
}
}
for (ll i = 1; i < N; i ++ ) pref[i] = (pref[i - 1] + i * i % mod * phi[i] % mod) % mod;
}
ll get_g(ll n)
{
n %= mod;
return n * (n + 1) % mod * (2 * n + 1) % mod * inv_6 % mod;
}
ll get_h(ll n)
{
n %= mod;
return (n * (n + 1) / 2 % mod) * (n * (n + 1) / 2 % mod) % mod;
}
ll get_sum(ll n)
{
if (n < N) return pref[n];
if (sumf[n]) return sumf[n];
ll res = get_h(n);
for (ll l = 2, r; l <= n; l = r + 1)
{
r = n / (n / l);
(res -= (get_g(r) - get_g(l - 1)) % mod * get_sum(n / l) % mod) %= mod;
}
res = (res + mod) % mod;
return sumf[n] = res;
}
int main()
{
cin >> mod >> n;
init(), inv_6 = ksm(6, mod - 2);
for (ll l = 1, r; l <= n; l = r + 1)
{
r = n / (n / l);
(ans += get_h(n / l) * ((get_sum(r) - get_sum(l - 1)) % mod) % mod) %= mod;
}
ans = (ans + mod) % mod;
cout << ans << endl;
return 0;
}