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. 直接寻址表 在介绍散列 ...
随机推荐
- Linux 函数: my_func
# A man and his 'fuctions' ;) # quick use ipmitool cmd to do something ipmi-ip-cmd () { local ip=$1 ...
- asp.net core之配置
简介 配置在asp.net core中可以说是我们必不可少一部分.ASP.NET Core 中的应用程序配置是使用一个或多个配置提供程序执行的. 配置提供程序使用各种配置源从键值对读取配置数据,普通最 ...
- 利用Redis实现向量相似度搜索:解决文本、图像和音频之间的相似度匹配问题
在自然语言处理领域,有一个常见且重要的任务就是文本相似度搜索.文本相似度搜索是指根据用户输入的一段文本,从数据库中找出与之最相似或最相关的一段或多段文本.它可以应用在很多场景中,例如问答系统.推荐系统 ...
- 类WPF跨平台模仿TIM
类WPF跨平台模仿TIM Avalonia是什么? Avalonia 是一个功能强大的框架,使开发人员能够使用 .NET 创建跨平台应用程序.它使用自己的渲染引擎来绘制UI控件,确保在各种平台上保持一 ...
- oracle 11g手工建库步骤(初学者)
要建立的数据库ORACLE_SID=test1sys和system的密码为oracle1.建立相应的目录mkdir /u01/app/oracle/oradata/test1mkdir /u01/ap ...
- rman catalog 遇到的一个错误
[oracle@source admin]$ sqlplus / as sysdba SQL*Plus: Release 11.2.0.3.0 Production on Thu Jun 22 09: ...
- 大二暑期实习记录(一):处理组件绑定数据错误(数组解构,map()方法)
好家伙,搬砖 今天在做组件迁移(从一个旧平台迁移到一个新平台)的时候,发现了一些小小的问题: 1.错误描述: 在穿梭框组件中,使用"节点配置"方法添加数据的时候,左测数据选择框 ...
- 双URL编码绕过WAF
一般编码一次是%5c. 但攻击者怕这个会被认出来,所以用二次编码,把%本身编码成%25.再和后边拼成%255c. 如果URL解码器有缺陷,只不断重复"从前边开始解析"这个步骤,就会 ...
- 【Unity3D】调整屏幕亮度、饱和度、对比度
1 屏幕后处理流程 调整屏幕亮度.饱和度.对比度,需要使用到屏幕后处理技术.因此,本文将先介绍屏幕后处理流程,再介绍调整屏幕亮度.饱和度.对比度的实现. 本文完整资源见→Unity3D调整屏幕 ...
- ATtiny88初体验(一):点灯
ATtiny88初体验(一):点灯 最近逛淘宝时,发现一块ATtiny88核心板(MH-ET LIVE Tiny88)用完红包后只剩4块钱了,果断下单,准备好好把玩一番. MH-ET LIVE Tin ...