[LOJ6235]区间素数个数
题目大意:
给定$n(n\leq10^{11})$,求$\pi(n)$。
思路:
计算$\pi$函数有$O(n^{\frac23})$的Lehmer算法,这里考虑$O(\frac{n^{\frac34}}{\ln n})$的洲阁筛。
我们可以将答案分为$\leq\sqrt n$的质数个数和$>\sqrt n$的质数个数。
其中$\leq\sqrt n$的质数个数可以线性筛预处理,而$>\sqrt n$的质数个数相当于用$\leq\sqrt n$的质数筛这$n$个数后剩下的数的个数。
若用$f[i][j]$表示$1\sim j$中与前$i$个数互质的数的个数,则转移方程为$f[i][j]=f[i-1][j]-f[i-1][\lfloor\frac j{p_i}\rfloor]$。$\pi(n)\sim\frac n{\ln n}$,$j$有$\sqrt n$种取值,时间复杂度$O\left(\frac n{\ln\sqrt n}\right)=O\left(\frac n{\ln n}\right)$。
当$p_{i+1}>j$时,$f[i][j]=1$。所以当$p_i>\frac j{p_i}$时,转移方程变为$f[i][j]=f[i-1][j]-1$。
因此对于每一个$j$,只需计算$p_i^2\leq j$的$f[i][j]$即可。对于$p_i^2>j$的$j$,可以记录最后一步的$i$是多少,转移的时候把那些$1$一起减掉。答案就是一开始线性筛求出的$\leq\sqrt n$的质数个数+用$\leq\sqrt n$筛完剩下的数。注意筛完除了那些$>\sqrt n$的质数,还会剩下$1$,因此最后要把$1$去掉。
时间复杂度$O\left(\frac{n^\frac34}{\ln n}\right)$。
1 #include<cmath> 2 #include<cstdio> 3 #include<cctype> 4 #include<algorithm> 5 typedef long long int64; 6 inline int64 getint() { 7 register char ch; 8 while(!isdigit(ch=getchar())); 9 register int64 x=ch^'0'; 10 while(isdigit(ch=getchar())) x=(((x<<2)+x)<<1)+(ch^'0'); 11 return x; 12 } 13 const int LIM=316228,P=27294; 14 bool vis[LIM]; 15 int lim,p[P],sum[LIM],last[LIM*2],cnt; 16 int64 val[LIM*2],f[LIM*2]; 17 inline void sieve() { 18 for(register int i=2;i<=lim;i++) { 19 if(!vis[i]) p[++p[0]]=i; 20 sum[i]=sum[i-1]+!vis[i]; 21 for(register int j=1;j<=p[0]&&i*p[j]<=lim;j++) { 22 vis[i*p[j]]=true; 23 if(i%p[j]==0) break; 24 } 25 } 26 } 27 int main() { 28 const int64 n=getint(); 29 lim=sqrt(n); 30 sieve(); 31 for(register int64 i=1;i<=n;i=n/(n/i)+1) { 32 val[++cnt]=n/i; 33 } 34 std::reverse(&val[1],&val[cnt]+1); 35 std::copy(&val[1],&val[cnt]+1,&f[1]); 36 for(register int i=1;i<=p[0];i++) { 37 for(register int j=cnt;j;j--) { 38 const int64 k=val[j]/p[i],pos=k<=lim?k:cnt+1-n/k; 39 if(k<p[i]) break; 40 f[j]-=f[pos]+last[pos]-i+1; 41 last[j]=i; 42 } 43 } 44 printf("%lld\n",sum[lim]+f[cnt]-1); 45 return 0; 46 }