1 条题解
-
1
P7884
简介
Meissel-Lehmer 是一种素数筛,适用于 超大的范围。
时间复杂度对比
算法名称 预处理时间复杂度 单次查询时间复杂度 空间复杂度 适用场景 Meissel-Lehmer 超大范围素数计数() 分段筛(Segment) 无 中等范围素数生成() 欧拉筛(Euler) 小范围素数预处理() 前置知识
P3383 欧拉筛
欧拉筛可以以 的方式求出区间 内的素数。
定义
- 表示第 个素数。
- 表示 内的素数个数。
- 表示 中不被前 个素数整除的数的个数。(包含 )
定理推导
定理一:Legendre 公式(基础拆分)
对于任意满足 的 和 ,有:
证明
一:所有素数 满足 () 的数包含于
因为 为素数,所以不会被前 个素数整除,满足 的定义,得证。
二: 中除 外,其余数均满足 ()
考虑反证:设 存在于符合 的定义且不满足 ()。 因为 是合数,存在正整数 满足 ,因为 ,所以有 。
又因为 ,所以 (),与假设矛盾,故得证。三:最终证明
由第二步得: 中除 外,其余数均满足 () 。
设 为大于 的素数数量和。
显然有:- 等量代换得:
得证。
定理二:如何计算
可以通过容斥原理与递归计算。
$$dp(x,k)=\begin{cases} x&(k=0)\\ 1&(p_k>x)\\ dp_{x,k}=\pi(x)-k+1&(p_k^2>x)\\ dp_{x,k}=dp_{x,k-1}-dp_{\lfloor x/p_k\rfloor,k-1} \end{cases}$$
我们有: 不被前 个素数整除的数 不被前 个素数整除的数 被 整除但不被前 个素数整除的数。
而“被 整除但不被前 个素数整除的数” 等价于“ 乘以不被前 个素数整除的数(且 )”,故数量为 。 故有:证明
对于任意 ,有
设:- $S=\{1≤m≤x|m\text{ 不被前 }k−1\text{ 个素数整除 }\},\text{则 }|S|=dp_{x,k−1}$。
- $T=\{1≤m≤x|m\text{ 不被前 }k\text{ 个素数整除}\},\text{则 }|T|=dp_{x,k}$。
第一步:分析 与 的关系
前 个素数是前 个素数加 ,因此“不被前 个素数整除” 等价于“不被前 个素数整除,且不被 整除”。
有:第二步:证明 $|\{m\in S|m \text{ 被 }p_k\text{ 整除} \}|=dp_{\lfloor x/p_k\rfloor,k-1}$
若 且 被 整除,则存在整数 使得 。 因为 所以 ;又因为 ,所以不被前 素数整除,得 不被前 个素数整除。(因为 与前 个素数互质,假设 被 整除,则 也被 整除,与 矛盾,故假设不成立)。
因为 且 不被前 个素数整除,则 ,且 不被前 个素数整除,得出 且 被 整除。
因此 中被 整除的数与 且 不被前 个素数整除的数是映射的,也就是两者的个数相等。
因为 不被前 个素数整除的数为 ,所以:$|\{m\in S|m \text{ 被 }p_k\text{ 整除} \}|=dp_{\lfloor x/p_k\rfloor,k-1}$
故得证。
根据定义,有:
$dp_{x,k}=|S|-|\{m\in S|m \text{ 被 }p_k\text{ 整除} \}|$
故得:
公式得证。定理三:最终公式
设 表示为大于 的两个素数的乘积小于 的个数。 当 很大时,直接计算 是很慢的,所以引入阈值 ,可以将 分为三部分:
接下来介绍各部分的含义:
- : 中的素数个数。
- :
- 不被前 个素数整除的数的个数减 1,包含大于 的素数和
- :
- 通过递归计算 得到( 是不被前 个素数整除的数的个数,减去 个素数,剩余的为 )。$Q(n,a) = \varSigma_{i=a+1}^{\pi(\sqrt{n})}(\pi(n/p_i)-i+1)$
证明
首先明确命题:
设 ,,则我们首先对 进行拆分:
- 第一类:对于 的素数,个数为 。
- 第二类:对于 的素数,设其个数为 ,则 ,故需要证明 。
现在来分析第二类素数的性质:
对于类 中的素数 ,考虑任意合数 ,保证 是 的最小因子 ,则 。
由于 ,得到 ,因 ,故 ,且
我们继续细分第二类质数对应的合数贡献:- 当 时,此时 的最小质因子为 ,对应的, 需要满足 且 。
- 当 时,此时 的最小质因子为 ,这类合数即为 。
接下来分别计算两种情况的贡献
第一种
考虑 ,有 (因为 ):
- 对于每个 ,对应的 需满足 且
- 这类 的个数是 。
因为当 时,,但 ,故 ,但 等价于 ,此时 ,此时 也需计入,所以实际个数要加 。
要对所以的 到 求和,所以第一种对于的素数个数为 $\varSigma_{i=a+1}^{b}(\pi(\lfloor n/p_i\rfloor)-i+1)$。
第二种
定义: 是“双素数乘积”。
同时,第二类的素数 满足“不是双素数乘积” 与“不被前 个素数整除”,结合 的定义,有:又因为( 是剔除 后的不被前 个素数整除的数,包含第二类素数和双素数乘积),所以有第二类素数个数 。
$$D=\varSigma_{i=a+1}^{b}(\pi(\lfloor n/p_i\rfloor)-i+1)-Q(n,a)$$
结合第一种情况,第二类素数个数 等于第一种个数 第二种对应的质数贡献,所以有:故得证。
实现思路
预处理小范围素数,再递归实现 与 。
明确定义
变量
- 表示第 个素数。
- 表是第 个数是否是素数。
- 表示 中有多少个素数。
- 表示小于 且不被前 个素数整除的个数(也就是 的预处理版本)。
定义常数部分
- 表示欧拉筛处理的范围上限。
- 表示 的第一维的大小。
- 表示 的第二维的大小。
一:欧拉筛
这个可以先套一下模板,但同时我们要维护 与 数组。
对于 数组,采用类似前缀和的方式维护:for(int i=1;i<N;i++)g[i]=g[i-1]+!ip[i];对于 数组,可以采用之前 的定义,有:
for(int i=1;i<=MX;i++)f[i][0]=i; for(int i=1;i<=MX;i++)for(int j=1;j<=MY;j++)f[i][j]=f[i][j-1]-f[i/p[j]][j-1];剩下的都是模板了,完整代码:
void shai(){ for(int i=2;i<N;i++){ if(!ip[i])p[++tot]=i; for(int j=1;j<=tot&&p[j]*i<N;j++){ ip[i*p[j]]=1; if(i%p[j]==0)break; } } for(int i=1;i<N;i++)g[i]=g[i-1]+!ip[i]; for(int i=1;i<=MX;i++)f[i][0]=i; for(int i=1;i<=MX;i++)for(int j=1;j<=MY;j++)f[i][j]=f[i][j-1]-f[i/p[j]][j-1]; }的计算
直接储存 是存不下的,我们可以考虑预处理小部分。
对于 ,有四种情况:情况 返回值 判定条件 解释 1 在预处理范围内。 2 到达边界。 3 剩余数均为素数。 4 不满足上述条件。 上文 的计算公式。 对于特殊情况的解释:当 时,小于 且不被前 个素数整除的数只能是素数(或 ),而 是小于 的素数总数,减去前 个素数(已被排除),得到结果。
基于此,得出以下代码:long long dp(long long i,long long j){ if(i<=MX&&j<=MY)return f[i][j]; if(!i||!j)return i; if(i<N&&1ll*p[j]*p[j]>=i)return max(0ll,g[i]-j); return dp(i,j-1)-dp(i/p[j],j-1); }的计算 (其实就是计算 )
当然,如果在预处理范围内就直接返回了。
定义 ,也就是 是小于 且不被前 个素数整除的数,加 是补上前 个素数(因前 个素数中排除了第 个),再减去多计算的合数,代码:long long P(long long n){ if(n<N)return g[n]-1; long long m=g[(long long)(pow(n,1./3))]-1,sum=dp(n,m)+m-1; for(m++;1ll*p[m]*p[m]<=n;m++)sum-=P(n/p[m])-P(p[m])+1; return sum; }完整代码
#include<bits/stdc++.h> using namespace std; const int N=8e6+10,MX=1.8e6,MY=60; long long n; int f[MX+5][MY+5],p[N/10],ip[N],g[N],tot; void shai(){ for(int i=2;i<N;i++){ if(!ip[i])p[++tot]=i; for(int j=1;j<=tot&&p[j]*i<N;j++){ ip[i*p[j]]=1; if(i%p[j]==0)break; } } for(int i=1;i<N;i++)g[i]=g[i-1]+!ip[i]; for(int i=1;i<=MX;i++)f[i][0]=i; for(int i=1;i<=MX;i++)for(int j=1;j<=MY;j++)f[i][j]=f[i][j-1]-f[i/p[j]][j-1]; } long long dp(long long i,long long j){ if(i<=MX&&j<=MY)return f[i][j]; if(!i||!j)return i; if(i<N&&1ll*p[j]*p[j]>=i)return max(0ll,g[i]-j); return dp(i,j-1)-dp(i/p[j],j-1); } long long P(long long n){ if(n<N)return g[n]-1; long long m=g[(long long)(pow(n,1./3))]-1,sum=dp(n,m)+m-1; for(m++;1ll*p[m]*p[m]<=n;m++)sum-=P(n/p[m])-P(p[m])+1; return sum; } int main(){ shai(); long long n; cin>>n; cout<<P(n); return 0; }后记
最后,感谢观看!
- 1
信息
- ID
- 11880
- 时间
- 5000ms
- 内存
- 512MiB
- 难度
- 9
- 标签
- 递交数
- 3
- 已通过
- 2
- 上传者