Meissel–Lehmer 算法
前言
推荐先行阅读我的blog文章————Min_25 筛
什么是Meissel–Lehmer 算法
Meissel-Lehmer 算法是一种基于 \(ϕ\) 函数的的快速计算前缀质数个数(当然也可以推广到前缀和质数幂次)的算法。
【模板例题】Meissel–Lehmer 算法
给定整数 \(n\),求出 \(\pi(n)\) 的值。
\(\pi(n)\) 表示 \(1 \sim n\) 的整数中质数的个数。
对于 \(100\%\) 的数据,\(1 \leq n \leq 10^{13}\)。
解法
前置知识:Eratosthenes 筛法。Eratosthenes 筛法是最简单的筛法之一
int Eratosthenes(int n) {
int p = 0;
for (int i = 0; i <= n; ++i) is_prime[i] = 1;
is_prime[0] = is_prime[1] = 0;
for (int i = 2; i <= n; ++i) {
if (is_prime[i]) {
prime[p++] = i;
if ((long long)i * i <= n) for (int j = i * i; j <= n; j += i)
is_prime[j] = 0; //(*)
}
}
return p;
}
我们只需要知道在标星号的那一行代码执行时有多少个 is_prime 从 true 变为 false 。如果我们可以维护这个值,我们就可以知道从 1 到 n 的素数个数了。据说,这个东西在 Project Euler 上曾经有一个所谓 Lucy DP 的东西可以解决,但是这个不是现在我们想要的。
定义: 记 is_prime 为 \(a\),\(S(v,p)\) 为 \(a_j ( 2\le j\le v)\)在 \(i=p\)时处理完毕后为 true 的个数。
然后上标处理,虽然时空复杂度达不到线性,但很快了。
之后修为Min_25筛发
在 min_25 的代码中的一些变量解释:
\(\texttt{smalls}[i] = S(i, p)\)
\(\texttt{roughs}[i]\):没有被筛掉的第 \(i\) 个整数。
其大小由一个变量 \(\texttt s\) 维护。
把 \(1\) 留下,过筛时使用的素数会消失。
\(\texttt{larges}[i] = S(\lfloor n/\texttt{roughs}[i]\rfloor, p)\)
\(\texttt{pc} = \pi(p-1)\)
\(\texttt{skip}[i]\):\(i\) 如果被筛出来的话就是真的。
但是不更新偶数。
using i64 = long long;
int isqrt(i64 n) {
return sqrtl(n);
}
__attribute__((target("avx"), optimize("O3", "unroll-loops")))
i64 prime_pi(const i64 N) {
if (N <= 1) return 0;
if (N == 2) return 1;
const int v = isqrt(N);
int s = (v + 1) / 2;
vector<int> smalls(s); for (int i = 1; i < s; ++i) smalls[i] = i;
vector<int> roughs(s); for (int i = 0; i < s; ++i) roughs[i] = 2 * i + 1;
vector<i64> larges(s); for (int i = 0; i < s; ++i) larges[i] = (N / (2 * i + 1) - 1) / 2;
vector<bool> skip(v + 1);
const auto divide = [] (i64 n, i64 d) -> int { return double(n) / d; };
const auto half = [] (int n) -> int { return (n - 1) >> 1; };
int pc = 0;
for (int p = 3; p <= v; p += 2) if (!skip[p]) {
int q = p * p;
if (i64(q) * q > N) break;
skip[p] = true;
for (int i = q; i <= v; i += 2 * p) skip[i] = true;
int ns = 0;
for (int k = 0; k < s; ++k) {
int i = roughs[k];
if (skip[i]) continue;
i64 d = i64(i) * p;
larges[ns] = larges[k] - (d <= v ? larges[smalls[d >> 1] - pc] : smalls[half(divide(N, d))]) + pc;
roughs[ns++] = i;
}
s = ns;
for (int i = half(v), j = ((v / p) - 1) | 1; j >= p; j -= 2) {
int c = smalls[j >> 1] - pc;
for (int e = (j * p) >> 1; i >= e; --i) smalls[i] -= c;
}
++pc;
}
larges[0] += i64(s + 2 * (pc - 1)) * (s - 1) / 2;
for (int k = 1; k < s; ++k) larges[0] -= larges[k];
for (int l = 1; l < s; ++l) {
int q = roughs[l];
i64 M = N / q;
int e = smalls[half(M / q)] - pc;
if (e < l + 1) break;
i64 t = 0;
for (int k = l + 1; k <= e; ++k) t += smalls[half(divide(M, roughs[k]))];
larges[0] += t - i64(e - l) * (pc + l - 1);
}
return larges[0] + 1;
}
仔细看上面的代码应该能大致了解算法,要是不懂可以看这里我的一些解释:
roughs 的更新:roughs[..s]是未过筛的整数。ns 是下一个循环中的 s。ns = 0,在 roughs[ns] 中有值的话 ns 就自增。
larges[smalls[d]-pc] 的含义:smalls[d] - pc 是 “未过筛剩下的整数中 d 是第几个数字?” 的意思。
smalls[i] 的更新:保持 \(j = \frac i p\)被更新。当 \(i \ge pj\) 时,smalls[i] 被更新,它的值被减去 smalls[j] - pc,也就是被减去 smalls[i / p] - pc。
完整代码
#include <stdio.h>
#include <cmath>
#include <algorithm>
#include <vector>
#define qaq inline
typedef long long ll;
qaq int isqrt(ll n){
return std::sqrt(n);
}
qaq ll half(ll n){
return (n-1)>>1;
}
qaq ll divide(ll n,ll base){
return double(n)/base;
}
ll piSieve(const ll n){
if(n<=1) return 0LL;
if(n==2) return 1LL;
const int lim=isqrt(n);
int vsz=(lim+1)>>1;
std::vector<int> smalls(vsz);
for(int cx=0;cx<vsz;++cx) smalls[cx]=cx;
std::vector<int> roughs(vsz);
for(int cx=0;cx<vsz;++cx) roughs[cx]=(cx<<1|1);
std::vector<ll> larges(vsz);
for(int cx=0;cx<vsz;++cx) larges[cx]=(n/(cx<<1|1)-1)>>1;
std::vector<bool> skips(lim+1);
int pCnt=0;
for(int p=3;p<=lim;p+=2){
if(skips[p]) continue;
int p2=p*p;
if(1LL*p2*p2>n) break;
skips[p]=true;
for(int cx=p2;cx<=lim;cx+=(p<<1))
skips[cx]=true;
int ns=0;
for(int cx=0;cx<vsz;++cx){
int cur=roughs[cx];
if(skips[cur]) continue;
ll d=1LL*cur*p;
larges[ns]=larges[cx]-(d<=lim?larges[smalls[d>>1]-pCnt]
:smalls[half(divide(n,d))])+pCnt;
roughs[ns++]=cur;
}
vsz=ns;
for(int cx=half(lim),cy=((lim/p)-1)|1;cy>=p;cy-=2){
int cur=smalls[cy>>1]-pCnt;
for(int cz=(cy*p)>>1;cz<=cx;--cx)
smalls[cx]-=cur;
}
++pCnt;
}
larges[0]+=1LL*(vsz+((pCnt-1)<<1))*(vsz-1)>>1;
for(int cx=1;cx<vsz;++cx) larges[0]-=larges[cx];
for(int cx=1;cx<vsz;++cx){
int q=roughs[cx];
ll m=n/q;
int e=smalls[half(m/q)]-pCnt;
if(e<cx+1) break;
ll t=0;
for(int cy=cx+1;cy<=e;++cy)
t+=smalls[half(divide(m,roughs[cy]))];
larges[0]+=t-1LL*(e-cx)*(pCnt+cx-1);
}
return larges[0]+1;
}
int main(){
ll n;
scanf("%lld",&n);
printf("%lld\n",piSieve(n));
return 0;
}
Meissel–Lehmer 算法的更多相关文章
- Meissel Lehmer Algorithm 求前n个数中素数个数 【模板】
Count primes Time Limit: 12000/6000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)Tot ...
- 求第 i 个素数 Meissel Lehmer Algorithm + 二分 【模板】
1473: L先生与质数V3 Time Limit: 1 Sec Memory Limit: 128 MBSubmit: 1348 Solved: 147 [Submit][Status][Web ...
- 【算法学习笔记】Meissel-Lehmer 算法 (亚线性时间找出素数个数)
「Meissel-Lehmer 算法」是一种能在亚线性时间复杂度内求出 \(1\sim n\) 内质数个数的一种算法. 在看素数相关论文时发现了这个算法,论文链接:Here. 算法的细节来自 OI w ...
- 洛谷 P3912 素数个数
P3912 素数个数 题目描述 求1,2,\cdots,N1,2,⋯,N 中素数的个数. 输入输出格式 输入格式: 1 个整数NN. 输出格式: 1 个整数,表示素数的个数. 输入输出样例 输入样例# ...
- [LOJ6235]区间素数个数
题目大意: 给定$n(n\leq10^{11})$,求$\pi(n)$. 思路: 计算$\pi$函数有$O(n^{\frac23})$的Lehmer算法,这里考虑$O(\frac{n^{\frac34 ...
- B树——算法导论(25)
B树 1. 简介 在之前我们学习了红黑树,今天再学习一种树--B树.它与红黑树有许多类似的地方,比如都是平衡搜索树,但它们在功能和结构上却有较大的差别. 从功能上看,B树是为磁盘或其他存储设备设计的, ...
- 分布式系列文章——Paxos算法原理与推导
Paxos算法在分布式领域具有非常重要的地位.但是Paxos算法有两个比较明显的缺点:1.难以理解 2.工程实现更难. 网上有很多讲解Paxos算法的文章,但是质量参差不齐.看了很多关于Paxos的资 ...
- 【Machine Learning】KNN算法虹膜图片识别
K-近邻算法虹膜图片识别实战 作者:白宁超 2017年1月3日18:26:33 摘要:随着机器学习和深度学习的热潮,各种图书层出不穷.然而多数是基础理论知识介绍,缺乏实现的深入理解.本系列文章是作者结 ...
- 红黑树——算法导论(15)
1. 什么是红黑树 (1) 简介 上一篇我们介绍了基本动态集合操作时间复杂度均为O(h)的二叉搜索树.但遗憾的是,只有当二叉搜索树高度较低时,这些集合操作才会较快:即当树的高度较高(甚至一种极 ...
- 散列表(hash table)——算法导论(13)
1. 引言 许多应用都需要动态集合结构,它至少需要支持Insert,search和delete字典操作.散列表(hash table)是实现字典操作的一种有效的数据结构. 2. 直接寻址表 在介绍散列 ...
随机推荐
- quarkus依赖注入之一:创建bean
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 关于依赖注入 对一名java程序员来说,依赖注入应该是 ...
- [k8s]使用私有harbor镜像源
前言 在node上手动执行命令可以正常从harbor拉取镜像,但是用k8s不行,使用kubectl describe pods xxx 提示未授权 unauthorized to access rep ...
- css里的position的static|relative|absolute|fixed的区别
前提:仅以div块为例,其它不清楚 注:1.下面截图看起来不是从页面左上角位置开始,是因为body元素有默认的margin,有边框 2.当position不是static时,top和bottom需要指 ...
- Linux 网络发包流程
哈喽大家好,我是咸鱼 之前咸鱼在<Linux 网络收包流程>一文中介绍了 Linux 是如何实现网络接收数据包的 简单回顾一下: 数据到达网卡之后,网卡通过 DMA 将数据放到内存分配好的 ...
- SpringBoot - 自定义starter
目录 一.什么是SpringBoot starter机制 二.为什么要自定义starter 三.什么时候需要创建自定义starter 四.自动加载核心注解说明 五.自定义starter的开发流程 案例 ...
- RK3568开发笔记(六):开发板烧写ubuntu固件(支持mipi屏镜像+支持hdmi屏镜像)
前言 编译了uboot,kernel,buildroot后,可以单独输入固件,也可以整体打包成rootfs进行一次性输入,rootfs直接更新升级这个方式目前也是常用的. 烧写器软件:RKDe ...
- springcache+redis实战
前言 有兴趣的同学,可以看我上一篇文章,然后再过来看会比较清楚点:https://www.cnblogs.com/yhc-910/p/14884678.html springcache,简单说,就是用 ...
- MIMO雷达中波形复用/分离的方法------TDMA\FDMA\DDMA\CDMA
最先接触到MIMO雷达的波形复用/分离的方法还是工作中负责的TI1843项目中了解的,主要还是时分多址波形(TDMA),当时刚接触时对这些很疑惑,再加上后面看到了频分多址波形(FDMA).码分多址波形 ...
- 使用C#发送邮件支持 Implicit SSL
安装Package: Install-Package AIM 使用下面的代码发送: class Mail { private static string mailAddress = "{yo ...
- windows下的Sonarqube+Sonar-Scanner简单入门配置
参考:https://blog.csdn.net/weixin_40496191/article/details/120250953 https://www.cnblogs.com/longpizi/ ...