前言

推荐先行阅读我的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 算法的更多相关文章

  1. Meissel Lehmer Algorithm 求前n个数中素数个数 【模板】

    Count primes Time Limit: 12000/6000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Tot ...

  2. 求第 i 个素数 Meissel Lehmer Algorithm + 二分 【模板】

    1473: L先生与质数V3 Time Limit: 1 Sec  Memory Limit: 128 MBSubmit: 1348  Solved: 147 [Submit][Status][Web ...

  3. 【算法学习笔记】Meissel-Lehmer 算法 (亚线性时间找出素数个数)

    「Meissel-Lehmer 算法」是一种能在亚线性时间复杂度内求出 \(1\sim n\) 内质数个数的一种算法. 在看素数相关论文时发现了这个算法,论文链接:Here. 算法的细节来自 OI w ...

  4. 洛谷 P3912 素数个数

    P3912 素数个数 题目描述 求1,2,\cdots,N1,2,⋯,N 中素数的个数. 输入输出格式 输入格式: 1 个整数NN. 输出格式: 1 个整数,表示素数的个数. 输入输出样例 输入样例# ...

  5. [LOJ6235]区间素数个数

    题目大意: 给定$n(n\leq10^{11})$,求$\pi(n)$. 思路: 计算$\pi$函数有$O(n^{\frac23})$的Lehmer算法,这里考虑$O(\frac{n^{\frac34 ...

  6. B树——算法导论(25)

    B树 1. 简介 在之前我们学习了红黑树,今天再学习一种树--B树.它与红黑树有许多类似的地方,比如都是平衡搜索树,但它们在功能和结构上却有较大的差别. 从功能上看,B树是为磁盘或其他存储设备设计的, ...

  7. 分布式系列文章——Paxos算法原理与推导

    Paxos算法在分布式领域具有非常重要的地位.但是Paxos算法有两个比较明显的缺点:1.难以理解 2.工程实现更难. 网上有很多讲解Paxos算法的文章,但是质量参差不齐.看了很多关于Paxos的资 ...

  8. 【Machine Learning】KNN算法虹膜图片识别

    K-近邻算法虹膜图片识别实战 作者:白宁超 2017年1月3日18:26:33 摘要:随着机器学习和深度学习的热潮,各种图书层出不穷.然而多数是基础理论知识介绍,缺乏实现的深入理解.本系列文章是作者结 ...

  9. 红黑树——算法导论(15)

    1. 什么是红黑树 (1) 简介     上一篇我们介绍了基本动态集合操作时间复杂度均为O(h)的二叉搜索树.但遗憾的是,只有当二叉搜索树高度较低时,这些集合操作才会较快:即当树的高度较高(甚至一种极 ...

  10. 散列表(hash table)——算法导论(13)

    1. 引言 许多应用都需要动态集合结构,它至少需要支持Insert,search和delete字典操作.散列表(hash table)是实现字典操作的一种有效的数据结构. 2. 直接寻址表 在介绍散列 ...

随机推荐

  1. pandas:字典转dataframe的注意事项

    推荐写法 参考链接 https://blog.csdn.net/u013061183/article/details/79497254

  2. pandas 根据内容匹配并获取索引

    bool = ExcelDataStr.str.contains("Item No./Customer/Saler") # 初始位置:initial position, 终位置:e ...

  3. selenium + python自动化环境搭建

    Selenium是一个用于Web应用程序测试的工具.Selenium测试直接运行在浏览器中,就像真正的用户在操作一样.支持的浏览器包括IE(7, 8, 9, 10, 11),Mozilla Firef ...

  4. 记录一次AutoMapper注册报错

    通常,.Net5我们注册服务是这样的. //添加AutoMapper var automapperConfog = new MapperConfiguration(config => { con ...

  5. Linux服务器的性能监控与分析

    通过vmstat分析性能  如上图所示,我们在命令vmstat后面添加了两个参数,1表示间隔一秒获取一次,10表示总共获取10次 我们一列一列数据来看: r:代表目前实际运行的指令队列,很高表示CPU ...

  6. vscode 中 Markdown 粘贴图片的位置

    背景 自从 typora 开始收费后, 不少人开始寻找其他的 Markdown编辑器, 我觉得 vscode 就是一个很不错的选择 虽然不能像 typora 在Markdown预览中编辑, 但是左右布 ...

  7. 通过WinSW部署JAR包为windows服务

    通过WinSW部署JAR包为windows服务 背景 使用 Java 编写了一些有用的工具,因为不方便部署到服务器上,所以需要把 Java 生成的 jar 包在本地 Windows 上部署. 查阅了几 ...

  8. RocketMQ系列(一) 基本介绍

    RocketMQ系列(一) 基本介绍 1.MQ 作用 MQ 的应用场景主要包含以下 3 个方面: 1.1.异步与解耦 当我们下了一个订单之后,订单服务会进行 RPC 同步调用 支付服务.库存服务.物流 ...

  9. Spring Bean 的作用域(Bean Scope)

    前言 大家好,我是 god23bin,今天我们来聊一聊 Spring 框架中的 Bean 作用域(Scope). 什么是 Bean 的作用域? 我们在以 XML 作为配置元数据的情况下,进行 Bean ...

  10. ora2pg使用记录

    ora2pg使用记录 前言 这篇文章是我在学习使用ora2pg过程中的学习记录,以便日后遗忘查阅: 诸君也可跟随我的步伐了解一下ora2pg,或可移步如下官方文档参考学习:Ora2Pg : Migra ...