1 条题解

  • 1
    @ 2025-10-7 16:10:57

    P7884

    简介

    Meissel-Lehmer 是一种素数筛,适用于 nn 超大的范围。

    时间复杂度对比

    算法名称 预处理时间复杂度 单次查询时间复杂度 空间复杂度 适用场景
    Meissel-Lehmer O(n2/3/logn)O(n^{2/3}/\log n) O(1)O(1) O(n2/3)O(n^{2/3}) 超大范围素数计数(n1012n \geq 10^{12}
    分段筛(Segment) O(nloglogn)O(n \log \log n) O(n)O(\sqrt{n}) 中等范围素数生成(n108n \leq 10^8
    欧拉筛(Euler) O(n)O(n) O(1)O(1) O(n)O(n) 小范围素数预处理(n107n \leq 10^7

    前置知识

    P3383 欧拉筛

    欧拉筛可以以 O(N)O(N) 的方式求出区间 [1,N][1,N] 内的素数。

    定义

    • pxp_{x} 表示第 xx 个素数。
    • π(x)\pi(x) 表示 [1,x][1,x] 内的素数个数。
    • dpx,kdp_{x,k} 表示 [1,x][1,x]不被前 kk 个素数整除的数的个数。(包含 11

    定理推导

    定理一:Legendre 公式(基础拆分)

    对于任意满足 k=π(n)k=\pi(\sqrt{n})nnkk,有:π(n)=k+(dpn,k1)\pi(n)=k+(dp_{n,k}-1)

    证明

    一:所有素数 pip_{i} 满足 (pk<pinp_{k}<p_{i}\le n) 的数包含于 dpn,kdp_{n,k}

    因为 pip_{i} 为素数,所以不会被前 kk 个素数整除,满足 dpn,kdp_{n,k} 的定义,得证。

    二:dpn,kdp_{n,k} 中除 11 外,其余数均满足 (pk<inp_{k}<i\le n)

    考虑反证:设 mm 存在于符合 dpn,kdp_{n,k} 的定义且不满足 (pk<mnp_{k}<m\le n)。 因为 mm 是合数,存在正整数 a,b>1a,b>1 满足 m=a×bm=a\times b,因为 k=π(n)k=\pi(\sqrt{n}),所以有 a×b=m    min(a,b)na\times b=m \implies \min{(a,b)}\le\sqrt{n}
    又因为 a,b>1a,b>1,所以 min(a,b)=pj\min{(a,b)}=p_{j}jkj\le k),与假设矛盾,故得证。

    三:最终证明

    由第二步得:dpn,kdp_{n,k} 中除 11 外,其余数均满足 (pk<pinp_{k}<p_{i}\le n) 。
    CC 为大于 pkp_{k} 的素数数量和。
    显然有:

    • C=dpn,k1C=dp_{n,k}-1
    • π(n)=k+C\pi(n)=k+C 等量代换得:π(n)=k+dpn,k1\pi(n)=k+dp_{n,k}-1
      得证。

    定理二:如何计算 dpn,kdp_{n,k}

    可以通过容斥原理递归计算。
    我们有: 不被前 kk 个素数整除的数 == 不被前 k1k−1 个素数整除的数 -pkp_k 整除但不被前 k1k−1 个素数整除的数。
    而“被 pkp_k 整除但不被前 k1k−1 个素数整除的数” 等价于“pkp_{k} 乘以不被前 k1k−1 个素数整除的数(且 x/pk\le x/p_k)”,故数量为 dpx/pk,k1dp_{x/p_k,k−1}。 故有:

    $$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}$$

    证明

    对于任意 x,k1x,k\ge 1,有 dpx,k=dpx,k1dpx/pk,k1dp_{x,k}=dp_{x,k-1}-dp_{\lfloor x/p_k\rfloor,k-1}
    设:

    • $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}$。

    第一步:分析 SSTT 的关系

    kk 个素数是前 k1k−1 个素数加 pkp_k,因此“不被前 kk 个素数整除” 等价于“不被前 k1k−1 个素数整除,且不被 pkp_k 整除”。
    有:T=S{mSm 被 pk 整除}|T|=|S|-|\{m\in S|m \text{ 被 }p_k\text{ 整除} \}|

    第二步:证明 $|\{m\in S|m \text{ 被 }p_k\text{ 整除} \}|=dp_{\lfloor x/p_k\rfloor,k-1}$

    mSm\in Smmpkp_k 整除,则存在整数 tt 使得 m=pk×tm=p_k\times t。 因为 mxm\le x 所以 tx/pkt\le \lfloor x/p_k\rfloor;又因为 mSm\in S,所以不被前 k1k-1 素数整除,得 tt 不被前 k1k-1 个素数整除。(因为 pkp_k 与前 k1k-1 个素数互质,假设 ttpi(i<k)p_i(i<k) 整除,则 m=pk×tm=p_k\times t 也被 pip_i 整除,与 mSm\in S 矛盾,故假设不成立)。
    因为 tx/pkt\le\lfloor x/p_k\rfloortt 不被前 k1k-1 个素数整除,则 m=pk×txm=p_k\times t\le x,且 mm 不被前 k1k-1 个素数整除,得出 mSm\in Smmpkp_k 整除。
    因此 SS 中被 pkp_k 整除的数与 1tx/pk1\le t\le\lfloor x/ p_k\rfloortt 不被前 k1k-1 个素数整除的数是映射的,也就是两者的个数相等。
    因为 tt 不被前 k1k-1 个素数整除的数为 dpx,kdp_{x,k},所以:$|\{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{ 整除} \}|$
    故得:dpx,k=dpx,k1dpx/pk,k1dp_{x,k}=dp_{x,k-1}-dp_{\lfloor x/p_k\rfloor,k-1}
    公式得证。

    定理三:最终公式

    Q(n,k)Q(n,k) 表示为大于 kk 的两个素数的乘积小于 nn 的个数。 当 nn 很大时,直接计算 dpx,kdp_{x,k} 是很慢的,所以引入阈值 a=π(n1/3)a=\pi(n^{1/3}),可以将 π(n)\pi(n) 分为三部分:

    π(n)=π(a)+(dpn,a1)Q(n,a)\pi(n)=\pi(a) + (dp_{n,a}-1) - Q(n,a)

    接下来介绍各部分的含义:

    • π(a)\pi(a)[1,a][1,a] 中的素数个数。
    • dpn,a1dp_{n,a}-1
      • 不被前 aa 个素数整除的数的个数减 1,包含大于 aa 的素数和 Q(n,a)Q(n,a)
    • Q(n,a)-Q(n,a)
      • 通过递归计算 dpn,a1(π(n)a)dp_{n,a}-1-(\pi(n)-a) 得到(dpn,a1dp_{n,a}-1 是不被前 aa 个素数整除的数的个数,减去 π(n)a\pi(n)-a 个素数,剩余的为 Q(n,a)Q(n,a))。$Q(n,a) = \varSigma_{i=a+1}^{\pi(\sqrt{n})}(\pi(n/p_i)-i+1)$

    证明

    首先明确命题:
    a=π(n1/3)a=\pi(n^{1/3})b=π(n)b=\pi(\sqrt{n}),则

    π(n)=π(a)+(dpn,a1)Q(n,a)\pi(n)=\pi(a) + (dp_{n,a}-1) - Q(n,a)

    我们首先对 π(n)\pi(n) 进行拆分:

    • 第一类:对于 piap_i\le a 的素数,个数为 π(a)\pi(a)
    • 第二类:对于 a<pina<p_i\le n 的素数,设其个数为 DD,则 π(n)=π(a)+D\pi(n)=\pi(a)+D,故需要证明 D=(dpn,a1)Q(n,a)D=(dp_{n,a}-1)-Q(n,a)

    现在来分析第二类素数的性质:
    对于类 22 中的素数 q(q>a)q(q>a),考虑任意合数 m=q×r(rqm=q\times r(r\ge q,保证 qqmm 的最小因子 )),则 mn    rn/qm\le n\implies r\le\lfloor n/q\rfloor
    由于 q>aq>a,得到 q>π(n1/3)q>\pi(n^{1/3}),因 pπ(x)>xp_{\pi(x)}>x,故 m=q×rq2>n2/3m=q\times r\ge q^2>n^{2/3},且 rn/q<n2/3r\le\lfloor n/q\rfloor<n^{2/3}
    我们继续细分第二类质数对应的合数贡献:

    • rar\le a 时,此时 m=q×rm=q\times r 的最小质因子为 rr,对应的,qq 需要满足 qn/rq\le\lfloor n/r\rfloorq>rq>r
    • r>ar>a 时,此时 m=q×rm=q\times r 的最小质因子为 qq,这类合数即为 Q(n,a)Q(n,a)

    接下来分别计算两种情况的贡献

    第一种

    考虑 a<piba<p_i\le b,有 pin1/2p_i\le n^{1/2}(因为 b=π(n)b=\pi(\sqrt{n})):

    • 对于每个 pi(a<ib)p_i(a<i\le b),对应的 qq 需满足 q>piq>p_iqn/piq\le\lfloor n/p_i\rfloor
    • 这类 qq 的个数是 π(n/pi)i+1\pi(\lfloor n/p_i\rfloor)-i+1。 因为当 q=piq=p_i 时,m=pi2m=p_i^2,但 pi>a=π(n1/3)>n1/3p_i>a=\pi(n^{1/3})>n^{1/3},故 pi2>n2/3p_i^2>n^{2/3},但 pin1/2p_i\le n^{1/2} 等价于 pi2np_i^2\le n,此时 pi2np_i^2\le n,此时 q=piq=p_i 也需计入,所以实际个数要加 11
      要对所以的 i=a+1i=a+1bb 求和,所以第一种对于的素数个数为 $\varSigma_{i=a+1}^{b}(\pi(\lfloor n/p_i\rfloor)-i+1)$。
    第二种

    定义:m=q×rm=q\times r 是“双素数乘积”。
    同时,第二类的素数 qq 满足“不是双素数乘积” 与“不被前 aa 个素数整除”,结合 dpn,adp_{n,a} 的定义,有:

    dpn,a1=Q(n,a)+ 第二类的素数个数 dp_{n,a}-1=Q(n,a)+\text{ 第二类的素数个数 }

    又因为(dpn,a1dp_{n,a}−1 是剔除 11 后的不被前 aa 个素数整除的数,包含第二类素数和双素数乘积),所以有第二类素数个数 =(dpn,a1)Q(n,a)=(dp_{n,a}-1)-Q(n,a)
    结合第一种情况,第二类素数个数 DD 等于第一种个数 ++ 第二种对应的质数贡献,所以有:

    $$D=\varSigma_{i=a+1}^{b}(\pi(\lfloor n/p_i\rfloor)-i+1)-Q(n,a)$$

    故得证。

    实现思路

    预处理小范围素数,再递归实现 dpdpπ(n)\pi(n)

    明确定义

    变量

    • pip_i 表示第 ii 个素数。
    • ipiip_i 表是第 ii 个数是否是素数。
    • gig_i 表示 [1,i][1,i] 中有多少个素数。
    • fi,jf_{i,j} 表示小于 ii 且不被前 jj 个素数整除的个数(也就是 dpi,jdp_{i,j} 的预处理版本)。

    定义常数部分

    • NN 表示欧拉筛处理的范围上限。
    • MXMX 表示 fi,jf_{i,j} 的第一维的大小。
    • MYMY 表示 fi,jf_{i,j} 的第二维的大小。

    一:欧拉筛

    这个可以先套一下模板,但同时我们要维护 ggff 数组。
    对于 gg 数组,采用类似前缀和的方式维护:

    for(int i=1;i<N;i++)g[i]=g[i-1]+!ip[i];
    

    对于 ff 数组,可以采用之前 dpdp 的定义,有:

    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];
    }
    

    dpi,jdp_{i,j} 的计算

    直接储存 dpi,jdp_{i,j} 是存不下的,我们可以考虑预处理小部分。
    对于 dpi,jdp_{i,j},有四种情况:

    情况 返回值 判定条件 解释
    1 fi,jf_{i,j} iMX 且 jMYi \le MX\text{ 且 }j \le MY 在预处理范围内。
    2 ii !i 或 !j!i\text{ 或 }!j 到达边界。
    3 max{0,gij}\max\{0,g_i-j\} i<N 且 1×pj×pjii<N\text{ 且 }1\times p_j\times p_j\ge i 剩余数均为素数。
    4 dpi,j1dpi/pj,j1dp_{i,j-1}-dp_{i/p_j,j-1} 不满足上述条件。 上文 dpi,jdp_{i,j} 的计算公式。

    对于特殊情况的解释:当 pj2ip_{j}^2\ge i 时,小于 ii 且不被前 jj 个素数整除的数只能是素数(或 11),而 gig_i 是小于 ii 的素数总数,减去前 jj 个素数(已被排除),得到结果。
    基于此,得出以下代码:

    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);
    }
    

    P(N)P(N) 的计算 (其实就是计算 π(n)\pi(n))

    当然,如果在预处理范围内就直接返回了。
    定义 sum=dpn,m+m1sum=dp_{n,m}+m-1,也就是 dpn,mdp_{n,m} 是小于 nn 且不被前 mm 个素数整除的数,加 m1m-1 是补上前 m1m-1 个素数(因前 mm 个素数中排除了第 mm 个),再减去多计算的合数,代码:

    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;
    }
    

    后记

    最后,感谢观看!

    信息

    ID
    11880
    时间
    5000ms
    内存
    512MiB
    难度
    9
    标签
    递交数
    3
    已通过
    2
    上传者