用途&复杂度
杜教筛可用于计算一些积性函数的前缀和:∑i=1nf(i)。
时间复杂度 n2/3,需要预处理出 n2/3 数据量的前缀和。
原理
令 S(n)=∑i=1nf(i)。
i=1∑ng(i)S(⌊in⌋)=i=1∑n(f∗g)(i)
以上就是杜教筛最关键的点。
证明:
i=1∑n(f∗g)(i)=i=1∑nd∣i∑g(d)f(⌊di⌋)=d=1∑ng(d)(i=1∑⌊dn⌋f(i))=d=1∑ng(d)S(⌊dn⌋)
我们要求的是 S(n),选择一个积性函数 g(i),则 g(1)=1。
由简单容斥可得
S(n)=g(1)⋅S(n)=i=1∑ng(i)⋅S(⌊in⌋)−i=2∑ng(i)⋅S(⌊in⌋)
减号前面的部分由上面的关键点可得等于 f 和 g 的卷积的前缀和。
所以有
S(n)=i=1∑n(f∗g)(i)−i=2∑ng(i)⋅S(⌊in⌋)
通过选择合适的积性函数 g,可以快速计算卷积的前缀和。而减号后面这部分可以通过整除分块计算,⌊in⌋ 的取值个数不超过 n 个。
例题
P4213 【模板】杜教筛(Sum)
求 莫比乌斯函数 以及 欧拉函数 的前缀和。
求莫比乌斯函数前缀和:
令 g(i)=1。
S(n)=i=1∑n(μ∗I)(i)−i=2∑nS(⌊in⌋)=i=1∑n[i=1]−i=2∑nS(⌊in⌋)=1−i=2∑nS(⌊in⌋)
注意先预处理出 n2/3 个前缀和。
后面的部分分块求。
求欧拉函数前缀和:
还是令 g(i)=1.
要知道 I∗φ=ID。其中 ID(x)=x。
S(n)=i=1∑n(φ∗I)(i)−i=2∑nS(⌊in⌋)=i=1∑nID(i)−i=2∑nS(⌊in⌋)=2(1+n)n−i=2∑nS(⌊in⌋)
也可以用莫比乌斯反演求。
S′(n)=i=1∑nj=1∑n[gcd(i,j)=1]=i=1∑nj=1∑nd∣gcd(i,j)∑μ(d)=d=1∑nμ(d)⋅⌊dn⌋2
由于 S(n)=∑i=1n∑j=1i[gcd(i,j)=1],所以 S(n)=2S′(n)−1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| #include<bits/stdc++.h> using namespace std; #define ll long long #define debug(x) cout << #x << ":\t" << x << endl; const int INF = 0x3f3f3f3f; const ll inf = 0x3f3f3f3f3f3f3f3f; const int N = 5e6 + 10; const ll mod = 998244353; int T; ll n; ll mu[N], phi[N], prime[N]; bool vis[N]; int cnt; void pre(ll n) { mu[1] = phi[1] = 1; for (ll i = 2; i <= n; i++) { if (!vis[i]) { prime[cnt++] = i; mu[i] = -1; phi[i] = i - 1; } ll d; for (int j = 0; j < cnt && (d = i * prime[j]) <= n; j++) { vis[d] = 1; if (i%prime[j] == 0) { mu[d] = 0; phi[d] = phi[i] * prime[j]; break; } else { mu[d] = -mu[i]; phi[d] = phi[i] * phi[prime[j]]; } } } for (int i = 1; i <= n; i++)mu[i] += mu[i - 1], phi[i] += phi[i - 1]; } map<ll, ll>smu, sp; ll S_mu(ll n) { if (n <= 5e6)return mu[n]; if (smu.count(n))return smu[n]; ll ans = 0; for (ll l = 2, r; l <= n; l = r + 1) { r = n / (n / l); ans += (r - l + 1)*S_mu(n / l); } return smu[n] = 1ll - ans; } ll S_p(ll n) { if (n <= 5e6)return phi[n]; if (sp.count(n))return sp[n]; ll ans = 0; for (ll l = 2, r; l <= n; l = r + 1) { r = n / (n / l); ans += (r - l + 1)*S_p(n / l); } return sp[n] = (1 + n)*n / 2 - ans; } int main() { pre(5e6); scanf("%d", &T); while (T--) { scanf("%lld", &n); printf("%lld %lld\n", S_p(n), S_mu(n)); } return 0; }
|
More
「LuoguP3768」简单的数学题
禁止动规