今天在看了各种以及听了各种之后终于算是了解线性筛了…
虽然都是一些很基本的应用但还是觉得各种强大…
线性筛素数
代码
int tot_prime, prime[maxn];
bool vist[maxn];
void get_prime(){
for(int i = 2; i <= n; ++i){
if(!vist[i]) prime[++tot_prime] = i;
for(int j = 1; i * prime[j] <= n && j <= tot_prime; ++j){
vist[i*prime[j]] = true;
if(i % prime[j] == 0) break;
}
}
}
一些解释
第一次看到的时候就有一句话觉得很鬼畜…
if(i % prime[j] == 0) break;
果然这段代码最鬼畜的就是这句,今天在翻其他东西的时候偶然发现了这个:
这行代码神奇地保证了每个合数只会被它的最小素因子筛掉,就把复杂度降到了 O(N)
接下来是证明这个算法正确性的说明:prime[] 数组中的素数是递增的,当 i 能整除
prime[j] ,那么 i∗prime[j+1] 这个合数肯定被 prime[j] 乘以某个数筛掉。
因为 i 中含有prime[j] , prime[j] 比 prime[j+1] 小,即 i=k∗prime[j] ,那么 i∗prime[j+1]=(k∗prime[j])∗prime[j+1]=k′∗prime[j] ,接下去的素数同理。所以不用筛下去了。因此,在满足 i 这个条件之前以及第一次满足改条件时, prime[j] 必定是 prime[j]∗i 的最小因子
求欧拉函数
相关公式
x=Πpaii
φ(x)=x−1 <x∈prime>
φ(x)=xΠ(1−1pi)=Π(pi−pai−1i)
除此只外,欧拉函数还是积性函数,即
φ(xy)=φ(x)φ(y) <gcd(x,y)=1>
代码
利用线性筛可以在线性时间内求得phi[i]
int tot_prime, prime[maxn], phi[maxn];
bool vist[maxn];
void get_prime(){
phi[1] = 1;
for(int i = 2; i <= n; ++i){
if(!vist[i]) prime[++tot_prime] = i, phi[i] = i - 1;
for(int j = 1; i * prime[j] <= n && j <= tot_prime; ++j){
vist[i*prime[j]] = true;
if(i % prime[j] == 0){
phi[i*prime[j]] = phi[i] * prime[j];
break;
}
else phi[i*prime[j]] = phi[i] * prime[j];
}
}
what’s more
但有时并不需要求
1...x
的所有欧拉函数值,很多时候我们要求的都是一个比较大的数字
x(x∈[1,109])
的欧拉函数值
虽然不需要筛法求欧拉函数了,但根据公式,还是需要筛素数
以下是筛素数之后的代码
int phi(int x){
int rtn = 1, cpy_x = x;
for(int i = 1; prime[i] * prime[i] <= cpy_x && i <= tot_prime; ++i){
int temp = 1;
while(x % prime[i] == 0){
x /= prime[i];
temp *= prime[i];
}
if(temp > 1) rtn *= temp - temp / prime[i];
}
if(x > 1) rtn *= (x - 1); // 还剩下一个很大的质数
return rtn;
}
求约数的个数
相关公式
设
f(x)
为
x
的约数的个数,还是把
f(x)=Π(ai+1)
f(x)=2 <x∈prime>
发现
f(x)
也是积性函数,即
f(xy)=f(x)f(y) <gcd(x,y)=1>
代码
为了方便,让 a[i] 表示 x 最小素数因子的个数
int tot_prime, prime[maxn];
int f[maxn], a[maxn];
bool vist[maxn];
void get_f(){
f[1] = 1;
for(int i = 2; i <= n; ++i){
if(!vist[i]) prime[++tot_prime] = i, f[i] = 2, a[i] = 1;
for(int j = 1; i * prime[j] <= n && j <= tot_prime; ++j){
vist[i*prime[j]] = true;
if(i % prime[j] == 0){
f[i*prime[j]] = f[i] / (a[i] + 1) * (a[i] + 2);
a[i*prime[j]] = a[i] + 1;
break;
}
else f[i*prime[j]] = f[i] * f[prime[j]], a[i*prime[j]] = 1;
}
}
}
一些解释
if(i % prime[j] == 0){
f[i*prime[j]] = f[i] / (a[i] + 1) * (a[i] + 2);
a[i*prime[j]] = a[i] + 1;
break;
}
当
else f[i*prime[j]] = f[i] * f[prime[j]], a[i*prime[j]] = 1;
当
对于a[]的转移,我是这样理解的:
我们先假设
i∗prime[j]
的最小素因子个数为1。
如果
i∗prime[j]
的最小素因子是由
i
提供的话,我们马上就会枚举到它的最小素因子,然后把
否则
i∗prime[j]
的最小素因子就是
prime[j]
且不被
i
包含。这是因为首先break
了,我们就不可能枚举到
求莫比乌斯函数
相关公式
μ(x)=1 <x=1>
μ(x)=−1k<x=Πki=1pi,pi的次数为1>
μ(x)=0<其他情况>
莫比乌斯函数同样是积性函数,即
μ(xy)=μ(x)μ(y) <gcd(x,y)=1>
。
代码
mu[1] = 1;
for(int i = 2; i <= n; ++i){
if(!vist[i]) prime[++tot_prime] = i, mu[i] = -1;
for(int j = 1; i * prime[j] <= n && j <= tot_prime; ++j)
vist[i*prime[j]] = true;
if(i % prime[j] == 0){
mu[i*prime[j]] = 0;
break;
}
else mu[i*prime[j]] = mu[i] * mu[prime[j]];
}
参考与其他
线性筛(欧拉筛)
【数论内容】线性筛素数,线性筛欧拉函数,求前N个数的约数个数
莫比乌斯反演ppt by PoPoQQQ
xiaohao1大神的讲解与莫比乌斯反演pdf