素数筛
普通写法: 获得[1,N]之间的所有素数
const int N=2e7+5; //注意一下,好想最大就2e7+9,如果是2e7+10就会炸,反正身边的电脑都是这样,一般不用开那么大
int prime[N];
// 获得[1,N]之间的素数,若prime[i]=0,表示i为素数
void getPrime()
{
memset(prime,0,sizeof(prime));
prime[0]=1;
prime[1]=1;
int temp=sqrt(N+0.5);
for(int i=2;i<=temp;i++)
{
if(!prime[i])
{
for(int j=i*i;j<N;j+=i)
{
prime[j]=1;
}
}
}
}
/*
使用:
i=1..N
if(!prime[i]) cout<<i<<endl;
*/
素数筛写法: 获得[1,N]之间的前prime[0]个素数
const int N=2e7+5;
int prime[N];
void getPrime()
{
memset(prime,0,sizeof(prime));
for(int i=2;i<=N;i++)
{
if(!prime[i])
{
prime[++prime[0]]=i;
}
for(int j=1;j<=prime[0]&&prime[j]*i<=N;j++)
{
prime[prime[j]*i]=1;
if(i%prime[j]==0)
break;
}
}
}
/*
使用:
i=1..prime[0]
cout<<prime[i]<<endl;
*/
解释:
程 序 的 流 程 : 程序的流程: 程序的流程:
首 先 i = 2.. N 如 果 p r i m e [ i ] 为 0 的 话 , 说 明 i 是 个 素 数 ( 1 ) 然 后 遍 历 j = 1.. p r i m e [ 0 ] , 同 时 p r i m e [ j ] ∗ i < = N 那 么 p r i m e [ j ] ∗ i 为 合 数 , 标 记 p r i m e [ p r i m e [ j ] ∗ i ] 为 1 如 果 出 现 i % p r i m e [ j ] = 0 , 说 明 i 是 素 数 p r i m e [ j ] 的 倍 数 ( 即 i = k ∗ p r i m e [ j ] ) , 那 么 就 可 以 结 束 以 i 为 倍 数 的 合 数 标 记 了 ( 2 ) 首先 i =2..N\\如果prime[i]为0的话,说明i是个素数 (^1)\\ 然后遍历j=1..prime[0],同时prime[j]*i<=N\\ 那么prime[j]*i为合数,标记prime[prime[j]*i] 为1\\ 如果出现i\%prime[j]=0,说明i是素数prime[j]的倍数(即i=k*prime[j]),那么就可以结束以i为倍数的合数标记了(^2) 首先i=2..N如果prime[i]为0的话,说明i是个素数(1)然后遍历j=1..prime[0],同时prime[j]∗i<=N那么prime[j]∗i为合数,标记prime[prime[j]∗i]为1如果出现i%prime[j]=0,说明i是素数prime[j]的倍数(即i=k∗prime[j]),那么就可以结束以i为倍数的合数标记了(2)
( 1 ^1 1):
首先要说明的是i一定已经被标记过(具体为啥,也不会证明)\
[1,prime[0]]中表示已经存好的素数,\ 因为当遍历到i时,且i一定大于素数prime[prime[0] ],\此时prime[i]还存的是合数标记,若i不是合数,就会被存入到prime[++prime[0]] 中$
( 2 ^2 2):\
若 i 0 % p r i m e [ j ] = = 0 ( 即 i 0 = K ⋅ p r i m e [ j ] ) , 则 b r e a k 跳 出 后 , 还 没 有 进 行 处 理 的 就 是 p r i m e 数 组 中 下 标 j 之 后 的 素 数 下 标 i n d e x ∈ ( j , p r i m e [ 0 ] ] , i 0 乘 以 它 们 , 都 可 以 转 换 成 k ∗ p r i m e [ j ] ∗ p r i m e [ i n d e x ] 的 形 式 , 也 就 是 说 当 i 2 = K ∗ p r i m e [ i n d e x ] ( 一 定 大 于 目 前 的 i 0 = K ∗ p r i m e [ j ] ) 时 , 同 时 遍 历 到 j 这 个 位 置 时 , 有 i 2 ∗ p r i m e [ j ] 就 等 于 i 0 ∗ p r i m e [ i n d e x ] ( = K ∗ p r i m e [ j ] ∗ p r i m e [ i n d e x ] ) 。 因 此 b r e a k 跳 出 后 , j 后 续 没 有 处 理 的 都 会 在 后 续 的 i 中 被 处 理 掉 若i_0\%prime[j]==0(即i_0=K\cdot prime[j]),则break跳出后,\\还没有进行处理的就是prime数组中下标j之后的素数下标index∈(j,\ prime[0]\ ],\\i_0乘以它们,都可以转换成k*prime[j]*prime[index]的形式,\\也就是说当i_2=K*prime[index](一定大于目前的i_0=K*prime[j])时,同时遍历到j这个位置时,\\有i_2*prime[j]就等于 i_0*prime[index](=K*prime[j]*prime[index])。\\因此break跳出后,j后续没有处理的都会在后续的i中被处理掉 若i0%prime[j]==0(即i0=K⋅prime[j]),则break跳出后,还没有进行处理的就是prime数组中下标j之后的素数下标index∈(j, prime[0] ],i0乘以它们,都可以转换成k∗prime[j]∗prime[index]的形式,也就是说当i2=K∗prime[index](一定大于目前的i0=K∗prime[j])时,同时遍历到j这个位置时,有i2∗prime[j]就等于i0∗prime[index](=K∗prime[j]∗prime[index])。因此break跳出后,j后续没有处理的都会在后续的i中被处理掉
( 3 ^3 3):\
如 何 证 明 每 个 数 只 被 标 记 了 一 次 , 素 数 不 会 被 标 记 , 只 要 考 虑 合 数 , 假 设 合 数 p r i m e [ j ] ∗ i 被 第 一 次 标 记 了 , 证 明 不 存 在 i 2 > i , 使 得 i 2 ⋅ p r i m e [ j 2 ] = p r i m e [ j ] ⋅ i 成 反 证 : 因 为 i 2 > i , 可 以 得 到 p r i m e [ j 2 ] < p r i m e [ j ] , 若 等 式 成 立 就 必 须 满 足 i % p r i m e [ j 2 ] = = 0 , 而 当 i % p r i m e [ j 2 ] = = 0 时 , 此 时 已 b r e a k , 就 不 会 遍 历 到 比 p r i m e [ j 2 ] 还 要 大 的 p r i m e [ j ] 那 , 也 就 不 会 有 p r i m e [ j ] ∗ i 被 标 记 , 故 不 存 在 这 样 的 i 2 , 证 毕 如何证明每个数只被标记了一次,素数不会被标记,只要考虑合数,假设合数prime[j]*i被第一次标记了,证明不存在i_2>i,使得i_2\cdot prime[j_2]=prime[j]\cdot i成 反证:因为i_2>i,可以得到prime[j_2]<prime[j],若等式成立就必须满足i\%prime[j_2]==0,而当i\%prime[j_2]==0时,此时已break,就不会遍历到比prime[j_2]还要大的prime[j]那,也就不会有prime[j]*i被标记,故不存在这样的i_2,证毕 如何证明每个数只被标记了一次,素数不会被标记,只要考虑合数,假设合数prime[j]∗i被第一次标记了,证明不存在i2>i,使得i2⋅prime[j2]=prime[j]⋅i成反证:因为i2>i,可以得到prime[j2]<prime[j],若等式成立就必须满足i%prime[j2]==0,而当i%prime[j2]==0时,此时已break,就不会遍历到比prime[j2]还要大的prime[j]那,也就不会有prime[j]∗i被标记,故不存在这样的i2,证毕
模拟打个表:
i | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
---|---|---|---|---|---|---|---|---|---|---|
prime[1..prime[0]] | 2 | 2,3 | 2,3 | 2,3,5 | 2,3,5 | 2,3,5,7 | 2,3,57 | 2,3,5,7 | 2,3,5,7 | 2,3,5,7,11 |
被标记的合数 | 4 | 6,9 | 8 | 10,15,25 | 12 | 14,21,35,49 | 16 | 18,27,45,63 | 20 | 22,33,55,77,121 |
例题:(需要用到欧拉筛方面的知识)
-
ACM-ICPC 2018 南京赛区网络预赛 J.sum----------------题解
-
51nod最大公约数之和 http://www.51nod.com/Challenge/Problem.html#problemId=1040---------------题解
-
HYSBZ - 4804 欧拉心算-----------------题解
欧拉函数筛
普通写法1: 获得一个数n的欧拉函数值
int euler_phi(int n)
{
int ans=n;
int tmp=sqrt(n+0.5);
for(int i=2;i<=tmp;i++) if(n%i==0)
{
ans=ans/i*(i-1);
while(n%i==0) n/=i;
}
if(n>1)
ans=ans/n*(n-1);
return ans;
}
普通写法2: 获得[1,n]所有的欧拉函数值
const int N=2e7+5;
int phi[N+1];
void phi_table(int n)
{
for(int i=2;i<=n;i++) phi[i]=0;
phi[1]=1;
for(int i=2;i<=n;i++) if(!phi[i]) //说明i是个素数,因此后面的操作的前提是i是素数
{
for(int j=i;j<=n;j+=i)
{
if(!phi[j]) phi[j]=j;
phi[j]=phi[j]/i*(i-1); // 合数j中有i这个质因子,进行处理
}
}
}
欧拉函数筛写法: 获得[1,n]所有的欧拉函数值
下面的证明部分来自TCgogogo
const int N=2e7+5;
int prime[N+1];
int phi[N+1];
void get_eular()
{
memset(prime,0,sizeof(prime));
phi[1]=1;
for(int i=2;i<=N;i++)
{
if(!prime[i])
{
prime[++prime[0] ]=i;
phi[i]=i-1;
}
for(int j=1;j<=prime[0]&&i*prime[j]<=N;j++)
{
prime[prime[j]*i]=true;
if(i%prime[j]==0)
{
phi[i*prime[j]] = phi[i]*prime[j];
break;
}
phi[i*prime[j]]=phi[i]*(prime[j]-1);
}
}
}
莫比乌斯函数筛
下面的证明部分来自TCgogogo
const int N=2e7+5;
int prime[N+1];
int mob[N+1];
void Mobius()
{
mob[1]=1;
for(int i=2;i<N;i++)
{
if(!prime[i])
{
prime[++prime[0]]=i;
mob[i]=-1;
}
for(int j=1;j<=prime[0]&& i*prime[j]<N;j++)
{
prime[i*prime[j]] =true;
if(i%prime[j]==0)
{
mob[i*prime[j]]=0;
break;
}
mob[i*prime[j]]=-mob[i];
}
}
}
积性函数
以下内容来自百度百科
参考资料:
- https://kuangbin.github.io/2018/09/01/2018-ACM-ICPC-Nanjing-online-J/
- https://blog.csdn.net/GodJing007/article/details/98075401
- https://blog.csdn.net/Tc_To_Top/article/details/48025849
- https://baike.baidu.com/item/%E7%A7%AF%E6%80%A7%E5%87%BD%E6%95%B0/8354949?fr=aladdin