卧薪尝胆,厚积薄发。
质数筛法
Date: Tue Jul 03 22:57:52 CST 2018 In Category: 总结

线性筛

筛出一张质数表。


for(register int i = 2;i < MAXN;++i)isprime[i] = true;
for(register int i = 2;i < MAXN;++i)
{
if(isprime[i])prime[++tot] = i;
for(register int j = 1;j <= tot && i * prime[j] < MAXN;++j)
{
isprime[i * prime[j]] = false;
if(i % prime[j] == 0)break;
}
}

求得一些积性函数值。


for(register int i = 2;i < MAXN;++i)isprime[i] = true;
phi[1] = 1;/*欧拉函数*/mu[1] = 1;/*莫比乌斯函数*/cnt[1] = 0;/*最小质因子出现次数*/
mdiv[1] = 1;/*最小质因子*/mdivs[1] = 1;/*最小质因子出现次数次方*/
sigma[1] = 1;/*约数和 除数函数*/numdiv[1] = 1;/*约数个数*/
for(register int i = 2;i < MAXN;++i)
{
if(isprime[i])
{
prime[++tot] = i;
phi[i] = i - 1;mu[i] = -1;cnt[i] = 1;
mdiv[i] = i;mdivs[i] = i;
sigma[i] = i + 1;
numdiv[i] = 2;
}
for(register int j = 1;j <= tot && i * prime[j] < MAXN;++j)
{
int k = i * prime[j];
isprime[k] = false;
mdiv[k] = prime[j];
if(i % prime[j] == 0)
{
phi[k] = phi[i] * prime[j];mu[k] = 0;cnt[k] = cnt[i] + 1;
mdivs[k] = mdivs[i] * mdiv[i];
sigma[k] = sigma[i / mdivs[i]] * (sigma[i] / sigma[i / mdivs[i]] * mdiv[i] + 1);
numdiv[k] = (cnt[k] + 1) * numdiv[i] / (cnt[i] + 1);
break;
}
else
{
phi[k] = phi[i] * phi[prime[j]];mu[k] = mu[i] * mu[prime[j]];cnt[k] = 1;
mdivs[k] = prime[j];
sigma[k] = sigma[i] * sigma[prime[j]];
numdiv[k] = numdiv[i] * numdiv[prime[j]];
}
}
}
若 $f(p^k)$ 可以在 $O(1)$ 时间内算得,则 $f(x)$ 可以在 $O(n)$ 时间内筛得 $[1,n]$ 中所有函数值。

杜教筛

在低于线性的时间复杂度内求得某个数论函数的前缀和。

原理

设要求 $\begin{align}F(n)=\sum_{i=1}^nf(i)\end{align}$ , $f(x)$ 是某个数论函数。
找到另外两个数论函数 $g$ 和 $h$ 使得 $f\times g=h$ ,其中 $\times$ 表示狄利克雷卷积,而 $g$ 和 $h$ 是两个方便求前缀和的函数。
$\begin{align}H(n)=\sum_{k=1}^n h(k)\end{align}$
$\begin{align}=\sum_{k=1}^n \sum_{d|k}g(d)f(\frac k d)\end{align}$
交换求和号,得:
$\begin{align}=\sum_{d=1}^n \sum_{d|k}g(d)f(\frac k d)\end{align}$
$\begin{align}=\sum_{d=1}^n \sum_{k=1}^{\lfloor\frac n d\rfloor}g(d)f(k)\end{align}$
$\begin{align}=\sum_{d=1}^ng(d)F(\lfloor\frac n d\rfloor)\end{align}$
于是 $\begin{align}g(1)F(n)=H(n)-\sum_{i=2}^ng(i)F(\lfloor\frac n i\rfloor)\end{align}$

复杂度证明

右边的 $F(\lfloor\frac n i\rfloor)$ 可以用除法分块优化到 $O(\sqrt n)$
由于 $\lfloor\lfloor n/i\rfloor/j\rfloor=\lfloor n/(ij)\rfloor$
所以使用记忆化搜索递归实现:每一层使用的 $F(\lfloor\frac n i\rfloor)$ 一定在第一层展开出现过,而第一层计算是 $O(\sqrt n)$ 的。
由于每一次要递归计算的值都会在最外层循环被计算,所以只会展开一层每次展开复杂度 $\sqrt x$ ,复杂度为:
$\begin{align}\sum_{i=1}^{\sqrt n}\sqrt{n / i}=O(n^{\frac 3 4})\end{align}$

优化

线性筛预处理出 $[1,n^{\frac 2 3}]$ 范围内的 $F$ 值,则复杂度降为 $O(n^{\frac 2 3})$

实现

筛 $\varphi$ 和 $\mu$ 的模板
筛 $\varphi$ 时, $g$ 选 $1$ , $h$ 选 $Id$ 。
筛 $\mu$ 时, $g$ 选 $1$ , $h$ 选 $\varepsilon$

#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<map>
#include<cstring>
using namespace std;
int testcases;
#define MAXN 5000010
typedef long long ll;
ll n;
map<int,ll> _phi,_mu;
ll phi[MAXN],mu[MAXN];
int prime[MAXN],tot = 0;
bool isprime[MAXN];
void init()
{
for(int i = 2;i < MAXN;++i)isprime[i] = true;
phi[1] = 1;
mu[1] = 1;
for(int i = 2;i < MAXN;++i)
{
if(isprime[i])
{
prime[++tot] = i;
phi[i] = i - 1;
mu[i] = -1;
}
for(int j = 1;j <= tot && i *prime[j] < MAXN;++j)
{
isprime[i * prime[j]] = false;
if(i % prime[j] == 0)
{
phi[i * prime[j]] = phi[i] * prime[j];
mu[i * prime[j]] = 0;
break;
}
else
{
phi[i * prime[j]] = phi[i] * (prime[j] - 1);
mu[i * prime[j]] = -1 * mu[i];
}
}
}
for(int i = 2;i < MAXN;++i)
{
phi[i] += phi[i - 1];
mu[i] += mu[i - 1];
}
return;
}
ll calcphi(ll n)
{
map<int,ll>::iterator it;
if(n < MAXN)return phi[n];
if((it = _phi.find(n)) != _phi.end())return it -> second;
ll ans = (((ll)n * (n + 1)) >> 1);
for(ll l = 2,r;l <= n;l = r + 1)
{
r = n / (n / l);
ans -= (r - l + 1) * calcphi(n / l);
}
return _phi[n] = ans;
}
ll calcmu(ll n)
{
map<int,ll>::iterator it;
if(n < MAXN)return mu[n];
if((it = _mu.find(n)) != _mu.end())return it -> second;
ll ans = 1;
for(ll l = 2,r;l <= n;l = r + 1)
{
r = n / (n / l);
ans -= (r - l + 1) * calcmu(n / l);
}
return _mu[n] = ans;
}
int main()
{
init();
scanf("%d",&testcases);
for(int i = 1;i <= testcases;++i)
{
scanf("%lld",&n);
printf("%lld %lld\n",calcphi(n),calcmu(n));
}
return 0;
}
杜教筛的重点是找到一组 $g$ 和 $h$ 使得 $g\times f=h$ ,一般不容易找到。

min

用于在低于线性的时间复杂度下求某个积性函数的前缀和。
具体来说时间复杂度是 $O(\frac{n^\frac 3 4}{\log n})$ 。
要求 $f(p)$ 是一个关于 $p$ 的简单多项式, $f(p^c)$ 可以快速计算。
先不考虑求前缀和,考虑求一个函数: $$ F(n)=\sum_{i=1}^n[i\ is\ prime]f(i) $$ 设: $$ g(n,j)=\sum^n_{i=1}[i\ is\ prime\ or\ minp(i)>p_j]f(i) $$ 含义就是 $i$ 是质数或者 $i$ 的最小质因子大于 $p_j$ ,把 $1\sim n$ 内所有满足条件的 $f(i)$ 加起来。
这个东西的实际意义就是埃氏筛法在筛掉前 $j$ 个素数后没被筛掉的所有数的 $f(i)$ 之和,包括所有的素数和还没有被筛掉的合数,因为这些合数 $minp(i)>p_j$ ,所以 $\min\_25$ 筛又被叫做埃筛的扩展。
考虑 $g(n,j)$ 的转移:
若 $p_j^2>n$ ,那显然这个质数不会筛掉任何其他数字, $g(n,j)=g(n,j-1)$ 。
若 $p_j^2\leqslant n$ ,那么被筛掉的数一定是最小质因子 $=p_j$ 的数,因为别的数要么还没被筛,要么已经筛掉了,被筛掉的数除掉 $p_j$ 之后最小质因子一定大于等于 $p_j$ ,因为原来最小质因子是 $p_j$ ,而且又由于 $f$ 是积性函数,因此我们可以提出来一个 $f(p_j)$ ,但是这个时候我们又多减掉了原来的质数,因此再加回来,于是有: $$ g(n,j)=g(n,j-1)-f(p_j)\bigl(g(\frac n{p_j},j-1)-\sum_{i=1}^{j-1}f(p_j)\bigr) $$ 但是我们的第一维还是 $n$ 个,但我们只要求 $g(n,|p|)$ ,而且又由于 $\lfloor\frac{\lfloor\frac n a\rfloor}{b}\rfloor=\lfloor\frac n{ab}\rfloor$ ,因此实际有用的 $n$ 由除法分块可知只有 $O(\sqrt n)$ 个,而且当 $p_j^2>n$ 的时候不转移,因此实际有用的质数也只有 $O(\sqrt{\frac n {\ln n}})$ 个,因此减少了复杂度。
如果让 $f(i)=1$ ,那么就有了一个 $\min\_25$ 筛的经典应用:求区间质数个数。
代码:

#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cctype>
#include<cstring>
using namespace std;
typedef long long ll;
ll n;
#define MAXN 10000001
#define N 10000000
bool isprime[MAXN];
ll prime[MAXN],tot = 0;
void sieve()
{
for(ll i = 2;i <= N;++i)isprime[i] = true;
for(ll i = 2;i <= N;++i)
{
if(isprime[i])prime[++tot] = i;
for(ll j = 1;j <= tot && i * prime[j] <= N;++j)
{
isprime[i * prime[j]] = false;
if(i % prime[j] == 0)break;
}
}
return;
}
ll id1[MAXN],id2[MAXN];
ll id(ll k){return (k <= N ? id1[k] : id2[n / k]);}
ll w[MAXN],m = 0;
ll g[MAXN];
int main()
{
sieve();
scanf("%lld",&n);
for(ll l = 1,r;l <= n;l = r + 1)
{
r = n / (n / l);
w[++m] = n / l;
if(w[m] <= N)id1[w[m]] = m;
else id2[n / w[m]] = m;
g[m] = w[m] - 1;
}
for(ll j = 1;j <= tot;++j)
{
for(ll i = 1;i <= m && prime[j] * prime[j] <= w[i];++i)
{
g[i] = g[i] - g[id(w[i] / prime[j])] + j - 1;
}
}
cout << g[1] << endl;
return 0;
}
然后我们继续考虑怎么用这个东西求积性函数前缀和,我们现在已经对所有 $x=\lfloor\frac n i\rfloor$ 求出了: $$ g(x,|p|)=\sum_{i=1}^x[i\ is\ prime]f(i) $$ 我们设: $$ S(n,j)=\sum_{i=1}^n[minp(i)\geqslant p_j]f(i) $$ 那么我们要求的答案就是 $S(n,1)+f(1)$ 。
现在考虑怎么求 $S(n,j)$ ,首先是质数的部分: $$ g(n,|p|)-\sum_{i=1}^{j-1}f(p_i) $$ 然后再考虑合数,枚举这个合数的最小质因子及其出现次数,然后由于积性把 $f$ 提出来: $$ S(n,j)=g(n,|p|)-\sum_{i=1}^{j-1}f(p_i)+\sum_{k=j}^{p_k^2\leqslant n}\sum_{e=1}^{p^{e+1}_k\leqslant n}\Bigl(S(\frac n{p^e_k},k+1)\times f(p^e_k)+f(p^{e+1}_k)\Bigr) $$ 这个的复杂度也是 $O(\frac{n^{\frac 3 4}}{\log n})$ 。
局限性:在筛 $g$ 的时候,要求 $f$ 完全积性,满足这个的函数一般有 $id^k$ 和 $1$ 。
不算模板。

#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cctype>
#include<cstring>
using namespace std;
typedef long long ll;
ll n;
#define MAXN 1000010
int N;
#define MOD 1000000007
#define INV2 500000004
int prime[MAXN],tot = 0;
int ps[MAXN];
bool isprime[MAXN];
void sieve()
{
for(int i = 2;i <= N;++i)isprime[i] = true;
for(int i = 2;i <= N;++i)
{
if(isprime[i])
{
prime[++tot] = i;
ps[tot] = (ps[tot - 1] + i) % MOD;
}
for(int j = 1;j <= tot && i * prime[j] <= N;++j)
{
isprime[i * prime[j]] = false;
if(i % prime[j] == 0)break;
}
}
return;
}
ll w[MAXN],m = 0;
int id1[MAXN],id2[MAXN];
inline int id(ll k){return (k <= N ? id1[k] : id2[n / k]);}
int pc[MAXN];
int g[MAXN];
int S(ll n,int j)
{//cout << n << " " << j << endl;
if(n <= 1 || prime[j] > n)return 0;
int k = id(n);
int res = (((g[k] - pc[k] + MOD) % MOD - ps[j - 1] + MOD) % MOD + j - 1) % MOD;
if(j == 1)res = (res + 2) % MOD;
for(int i = j;i <= tot && 1ll * prime[i] * prime[i] <= n;++i)
{
ll p1 = prime[i],p2 = 1ll * prime[i] * prime[i];
for(int e = 1;p2 <= n;++e,p1 = p2,p2 *= prime[i])
{
res = ((res + 1ll * S(n / p1,i + 1) * (prime[i] ^ e) % MOD) % MOD + (prime[i] ^ (e + 1))) % MOD;
}
}
return res;
}
int main()
{
cin >> n;
N = sqrt(n);
sieve();
for(ll l = 1,r;l <= n;l = r + 1)
{
r = n / (n / l);
w[++m] = n / l;
if(w[m] <= N)id1[w[m]] = m;
else id2[n / w[m]] = m;
pc[m] = (w[m] - 1) % MOD;
g[m] = ((w[m] + 2) % MOD) * ((w[m] - 1) % MOD) % MOD * INV2 % MOD;
}
for(int j = 1;j <= tot;++j)
{
for(int i = 1;i <= m && 1ll * prime[j] * prime[j] <= w[i];++i)
{
int k = id(w[i] / prime[j]);
pc[i] = ((pc[i] - pc[k] + MOD) % MOD + j - 1) % MOD;
g[i] = (g[i] - 1ll * prime[j] * ((g[k] - ps[j - 1] + MOD) % MOD) % MOD + MOD) % MOD;
}
}
cout << (S(n,1) + 1) % MOD << endl;
return 0;
}
In tag:
Copyright © 2020 wjh15101051
ღゝ◡╹)ノ♡