2 条题解
-
1
简单学习 Meissel–Lehmer 算法 后可得(反正我没看懂,以下由 生成):
1. 核心辅助函数
定义 为不超过 且不能被前 个质数整除的正整数个数。它满足递推关系:
$$\phi(x, a) = \phi(x, a-1) - \phi\left(\left\lfloor \frac{x}{p_a} \right\rfloor, a-1\right)$$其含义是:不被前 个质数整除的数 = 不被前 个质数整除的数 − 其中能被 整除的数。
当 时,所有不超过 且不含前 个质因子的数只能是 1 或大于 的质数,因此可直接计算为 ,无需继续递归。这是算法高效的关键剪枝。
2. Meissel-Lehmer 主公式
选取参数 ,则有:
$$\pi(n) = \phi(n, a) + a - 1 - \sum_{\substack{p_i > n^{1/3} \\ p_i^2 \le n}} \left( \pi\left(\left\lfloor \frac{n}{p_i} \right\rfloor\right) - \pi(p_i) + 1 \right)$$公式推导逻辑:
- 统计了所有不超过 且最小质因子 的数(包括 1 和所有大质数)。
- 由于 ,这样的合数至多有两个质因子(因为三个 的质数之积已超过 )。
- 因此 等于 加上所有形如 (其中 )的合数个数。
- 求和项正是对这些“双大质因子合数”的精确扣除:对每个满足条件的 ,合法的 的个数为 。
3. 复杂度控制原理
- 选择 使得 的递归深度和状态数被限制在 量级。
- 求和项中的 范围为 ,数量为 ,且每次递归调用 的参数 ,可通过预处理的小范围 值或相同方法快速求解。
- 结合 函数的平方剪枝和小范围记忆化,整体时间复杂度稳定为 ,空间复杂度为 级别。
#include<bits/stdc++.h> #define int long long using namespace std; constexpr int N=10000010,M=1200010; int f[M+5][65],p[N/10],g[N],cnt; bool ip[N]; inline void init(){ for(int i=2;i<N;i++){ if(!ip[i])p[++cnt]=i; for(int j=1;j<=cnt&&p[j]*i<N;j++){ ip[i*p[j]]=1; if(i%p[j]==0)break; } } ip[1]=1; for(int i=1;i<N;i++)g[i]=g[i-1]+!ip[i]; for(int i=1;i<=M;i++)f[i][0]=i; for(int i=1;i<=M;i++) for(int j=1;j<=60;j++) f[i][j]=f[i][j-1]-f[i/p[j]][j-1]; } int phi(int i,int j){ if(i<M&&j<=60)return f[i][j]; if(!i||!j)return i; if(i<N&&p[j]*p[j]>=i)return max(0LL,g[i]-j+1); return phi(i,j-1)-phi(i/p[j],j-1); } int pi(int n){ if(n<N)return g[n]; int k=pow(n,1.0/3); int a=g[k]; int res=phi(n,a)-1+a; for(int i=a+1;(int)p[i]*p[i]<=n;i++) res-=pi(n/p[i])-pi(p[i])+1; return res; } signed main(){ ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); init(); int n; cin>>n; cout<<pi(n); return 0; } -
0
对于公式做些补充与解释:
考虑取 ,那么 ,此时,公式的前半部分变为 ,也就是 范围内最小质因子 以内的最大值数的数的个数 以内的质数个数(因为它们在第一部分被排除掉了)(把 排除掉)
对于后半部分,我们考察 里面包含了什么,显然有着两部分:
1. 的所有质数。
2.由两个 的质数乘出的合数( 个乘积必定大于 )。
我们只想要第一部分,所以要把第二部分减掉。
我们发现第二部分的每一个数都可以被表示成 的形式(其中 且 均为质数同时 )。考虑钦定每一个 去寻找对应的 ,注意到 需要同时满足以下几个条件:
1.,否则 。
2.,毕竟我们就是这么规定的。
所以可行的 的数量就是 区间内的质数数量,用前缀和转化一下就是 (也可以向上面那样写)。
结合起来就是:
$$\pi(n)=\phi(n,\pi(n^{\frac{1}{3}}))+\pi(n^{\frac{1}{3}})-1-\sum_{n^{\frac{1}{3}}\le p\le \sqrt{n},p\ is\ prime}{\pi(\lfloor\frac{n}{p}\rfloor)-\pi(p-1)}$$代码:
#include<bits/stdc++.h> #define int long long using namespace std; const int n=1e7+10,m=1.2e6+10; int phi[m+5][65],pri[n/10],pre[n],cnt; // ^ ^ ^ // 就是$\phi$ 质数表 <=i的质数数量 bool is[n]; // ^ //是否是质数 //预处理 void init(){ //欧拉筛筛质数 for(int i=2;i<=1e7;i++){ if(!is[i])pri[++cnt]=i; for(int j=1;j<=cnt && pri[j]*i<=1e7;j++){ is[pri[j]*i]=1; if(i%pri[j]==0)break; } } is[1]=1; //前缀和数组处理 for(int i=1;i<=1e7;i++)pre[i]=pre[i-1]+!is[i]; //显然p[0]=0,所以phi[i][0]=i; for(int i=1;i<=m;i++)phi[i][0]=i; //利用公式递推 for(int i=1;i<=m;i++){ for(int j=1;j<=60;j++){ phi[i][j]=phi[i][j-1]-phi[i/pri[j]][j-1]; } } } //公式的第一项 int first(int i,int j){ //处理过了就直接返回 if(i<=m && j<=60)return phi[i][j]; //有一个为0就直接返回i if(!i || !j)return i; //p[j]*p[j]>=i时,所有满足条件的数就是<=i的质数个数-j+1(把p_j补上) if(i<n && pri[j]*pri[j]>=i)return max(0LL,pre[i]-j+1); //调公式 return first(i,j-1)-first(i/pri[j],j-1); } //求答案 int pi(int x){ if(x<n)return pre[x]; int k=pow(x,1.0/3); int a=pre[k]; int res=first(x,a)+a-1; for(int i=a+1;pri[i]*pri[i]<=x;i++){ res-=pi(x/pri[i])-pi(pri[i]-1); } return res; } signed main(){ int num; cin>>num; init(); cout<<pi(num); return 0; }
- 1
信息
- ID
- 3251
- 时间
- 1000ms
- 内存
- 1024MiB
- 难度
- 9
- 标签
- 递交数
- 16
- 已通过
- 3
- 上传者