素数计数函数

一种计算 π(n) 的组合方法

这里 π(n) 指:不大于 n 的素数个数

注:本文方法来自于维基百科对素数计数的一个组合方法:https://en.wikipedia.org/wiki/Prime-counting_function
由于最后的一些优化技巧还未掌握。文中的方法还有待优化。
与原文的方法略微不同。与洲阁筛的方法近似。。。

常识:一般来说,x以内的素数大约有:

π(x)=O(xln x)

那么我们可以猜测埃式筛法的复杂度:

T(n)=O(npn1p)

可以认为是线性的。
n 不是很大的时候。埃式筛法是个不错的选择。(不知道素数筛法的可以自行百度。)

埃式筛法在计算π(n)时。还会得到素数表。

像埃式筛法这样:通过得到所有不大于 n 的素数,来计算π(n)。算法复杂度的下界是 O(nlogn) 。不会很快。

<script type="math/tex; mode=display" id="MathJax-Element-12"></script>

组合方法:

对于一个整数 q q(n,n]
q 如果不能被[2,n]的任何整数整除。
则: q 是一个素。

很容易得到一个基于容斥原理的式子:

[1,n]中,不能被前 m 个素数整除的数字个数为:

C(m,n)=n1imnPi+1i<jmnPiPj1i<j<kmnPiPjPk...

Pi 指第 i 个素数

则:

C(π(n),n)=π(n)π(n)+1

C(m,n)=C(m1,n)C(m1,nPm)

对于第二个式子。
[1,n] 中不能被前 m1 个素数整除的数字包含:
可以被 Pm 整除的数字。但不能被前 m1 个素数整除的数字
对于 Pm 的所有倍数:

kPm,   k[1,nPm]

不能被前 m1 个素数整除的 k 的数量为:

C(m1,nPm)

所以:

C(m,n)=C(m1,n)C(m1,nPm)

特别的:

C(0,n)=n

方便起见,下文所有数均为整数。

对于 C(m,n) 定义:

D(m,d)=C(m,nd)

因为:

nab=nab

所以:

D(m,d)=D(m1,d)D(m1,dPm)

那么,通过D数组与C数组交替递推。并且选择一个恰当的分界 B 我们计算D[][1...B] C[][1...nB+1]

有:
dPmB:D[m][d]=D[m1][d]D[m1][dPm]dPm>B:D[m][d]=D[m1][d]C[m1][ndPm]

<script type="math/tex; mode=display" id="MathJax-Element-44"></script>

C[m][j]=C[m1][j]C[m][jPm]

B=n 时,这样计算的复杂度: O(nlogn)

并不比前面说到的方法快多少。

优化1:

通过上面的容斥原理很容易有:

<script type="math/tex; mode=display" id="MathJax-Element-48"></script>

Pm+1>n 时:

C(m,n)=1

则: P2m>n 时:

C(m,n)=C(m1,n)C(m1,nPm)=C(m1,n)1

优化2:

P2m(nB+1)14 时:

C(m,j)=π(max(Pm,j))m+1

综上 。我们分两段递推:

第一段,前: Pm(nB+1)14 。正常计算

第二段。此时 P2m>(nB+1)14

对于第二个优化。预处理出前 n π[] .
此时不在更新 C[][]
如果需要C数组的信息。利用:优化2的式子得到。
对于D数组,应用优化1:

P2m>nd 时,即: d>nP2m

D(m,d)=D(m1,d)1

可以肯定的是。不对 d[nP2m+1,B] 更新

仅仅维护 d[1,nP2m] 时。

我们记录最早开始不更新的哪个素数标号。并预处理前缀和。必要时刻查前缀和的表。即可。对于没有记录的区间有需要更新的区间。我们暴力更新。时间复杂度不会增加。(可以自行证明。很简单)

还有一种笨的方法。也不会很慢

笨的方法就是:使用区间数据结构来维护(总复杂度多了个 log?)。

建议使用数状数组。(常数小。也就慢了700ms。。。。

所以第二阶段。我们仅仅计算了 d[1,nP2m]

那么总多时间复杂度:

T(n)=P(nB+1)1/4(O(B)+O(nB))+(nB+1)1/4<Pn1/2O(np2)

B=n 时: T(n)=O(n34logn)

但是中间维护咱们不是 O(1)

使用数据结构的话是: T(n)=O(n34)

注意:修改对C的定义。让其变为[2,n]上的数并不会优化计算。这是因为中间的递推依然会多出来一个余项。

下面是代码。。。(可以快速计算100亿以内的答案。)

#include <algorithm>
#include <stdio.h>
#include <string.h>
#include <cmath>
#define MAXN 1111111
using namespace std;
typedef long long LL;
struct arry
{
    int A[MAXN];
    int n;

    arry()
    {
        memset(A,0,sizeof A);
    }

    void clear(int m)
    {
        memset(A,0,(m+1)*sizeof(int));
        n=m;
    }

    int lowbit(int x)
    {
        return x&(-x);
    }

    void add(int x,int key)
    {
        while(x<=n)
        {
            A[x]+=key;
            x+=lowbit(x);
        }
    }

    int sum(int x)
    {
        int ans=0;
        while(x)
        {
            ans+=A[x];
            x-=lowbit(x);
        }
        return ans;
    }
}S;

int prim[MAXN],deep=1;
int pi[MAXN];
LL G[MAXN];
LL C[MAXN];

void init()
{
    for(int i=2;i<MAXN;i++)pi[i]=1;
    for(int i=2;i<MAXN;i++)
    {
        if(!pi[i])continue;
        prim[deep++]=i;
        for(int j=i<<1;j<MAXN;j+=i) pi[j]=0;
    }
    for(int i=2;i<MAXN;i++) pi[i]+=pi[i-1];
}

void clat_1(int m,int k,LL n)
{
    LL p=prim[k];
    for(int i=1;i<m;i++)
    {
        LL d=i*p;
        LL u=n/d;
        if(u<m)
            G[i]-=C[u];
        else
            G[i]-=G[d];
    }
    for(int i=m;i;i--)C[i]-=C[i/p];
}

LL slove(LL n)
{
    if(n<MAXN)return pi[n];
    int m=sqrt(n)+1.1;
    int n_4=pow(n,1.0/4.0)+1.1;
    for(int i=1;i<=m;i++)
    {
        C[i]=i;
        G[i]=n/i;
    }
    int k;
    for(k=1;prim[k]<n_4;k++)clat_1(m,k,n);
    S.clear(m);
    while(prim[k]<m)
    {
        LL p=(LL)prim[k]*prim[k];
        LL lim=n/p+1;
        for(int d=1;d<lim;d++)
        {
            LL u=(LL)d*prim[k];
            LL b=n/u;
            if(b<m)
            {
                if(b<=prim[k-1])
                    G[d]-=1;
                else
                    G[d]-=pi[b]-k+2;
            }
            else
                G[d]-=G[u]-S.sum((int)u);
        }
        S.add((int)lim,1);
        k++;
    }
    return k+G[1]-2;
}

int main ()
{
    init();
    LL n;
    while(scanf("%lld",&n)==1)  printf("%lld\n",slove(n));
    return 0;
}

增加内容(与维基百科上的一致):

上面的递推有一个简化和推广。(其实是针对上面递推变形出来的一个)

记: Gk(i,j) 表示: [1,j] 上,由 k 个大于Pi的素数组成的数的数量。

那么有
C(i,j)=k=0Gk(i,j)

那么:
C(π(j13),j)=k=0Gk(π(j13),j)=k=02Gk(π(j13),j)+k=3Gk(π(j13),j)

明显1:
k=3Gk(π(j13),j)=0

明显2:
G0(i,j)=1G1(i,j)=π(max(j,Pi))i

<script type="math/tex; mode=display" id="MathJax-Element-79"></script>

对于 G2(π(j13),j) .它的可选素数是有 P>j13

那么对于另一个素数 P>j13 ,且 PPj , PP .

这样的素数 P 个数为:

π(jP)π(P)+1

所以:
G2(π(j13),j)=j1/3<Pj(π(jP)π(P)+1)

综上:

π(j)=C(π(j13),j)G0(π(j13),j)G2(π(j13),j)+π(j13)

这种方法 。感觉前一部还是要打表出 n1/4 比较好。我比较弱。还是不知道优化的优雅方法。

这种方法空间复杂度比较高。有些人貌似是部分记忆话。效率还很高呢。

#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<math.h>
using namespace std;
typedef long long ll;
#define INF 30000000000
#define chk(pos, i) (((pos[i/64])&(1<<((i>>1)&31))))
#define set(pos, i) (((pos[i/64])|=(1<<((i>>1)&31))))
#define check(x) ((x&&(x&1)&&(!chk(pos, x)))||(x==2))
const int N = 101;
const int M = 49500;
const int P = 700000;
const int UP = 5000000;
ll tmp[N][M];
unsigned int pos[157000] = {0};
int len = 0, pm[P], cnt[UP];
void init(){
    set(pos, 0), set(pos, 1);
    for(ll i = 3; i*i < UP; i += 2){
        if(!chk(pos, i)){
            ll k = i<<1;
            for (ll j = (i * i); j < UP; j += k) set(pos, j);
        }
    }
    for(int i = 1; i < UP; ++i){
        cnt[i] = cnt[i-1];
        if(check(i)) pm[len++] = i, cnt[i]++;
    }
    for(ll n = 0; n < N; ++n){
        for(int m = 0; m < M; ++m){
            if(!n){ tmp[n][m] = m; continue; }
            tmp[n][m] = tmp[n-1][m]-tmp[n-1][m/pm[n - 1]];
        }
    }
}
ll euler(ll m, int n){
    if(n == 0) return m;
    if(pm[n - 1] >= m) return 1;
    if(m < M && n < N) return tmp[n][m];

    return euler(m, n - 1) - euler(m / pm[n - 1], n - 1);
}
ll solve(ll m){
    if(m < UP) return cnt[m];
    int s = sqrt(m+0.9);
    int y = pow(m+0.9,1.0/3.0);
    ll res = euler(m, cnt[y])+cnt[y]-1;
    for (int i = cnt[y]; pm[i] <= s; i++){
        res += - solve(m/pm[i]) + solve(pm[i]) - 1;
    }
    return res;
}
int main() {
    init();
    ll n;
    while(scanf("%lld",&n)==1)printf("%lld\n",solve(n));
    return 0;
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 可以使用filter函数和is_prime函数来统计列表中所有非素数。 具体步骤如下: 1. 定义is_prime函数,判断一个数是否为素数。 2. 使用filter函数,筛选出列表中所有非素数。 3. 使用len函数,统计非素数的个数。 示例代码如下: ```python def is_prime(n): if n < 2: return False for i in range(2, int(n ** 0.5) + 1): if n % i == 0: return False return True lst = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] non_primes = list(filter(lambda x: not is_prime(x), lst)) count = len(non_primes) print(non_primes) # [1, 4, 6, 8, 9, 10] print(count) # 6 ``` 输出结果为: ``` [1, 4, 6, 8, 9, 10] 6 ``` ### 回答2: 素数指的是只能被1和本身整除的正整数,例如2、3、5、7等。因此,非素数就是除了素数以外的正整数。 要使用filter函数统计列表中所有非素数,首先需要定义一个判断数字是否为素数函数。例如,可以使用以下函数: ```python def is_prime(num): if num <= 1: return False for i in range(2, int(num ** 0.5) + 1): if num % i == 0: return False return True ``` 这个函数首先判断数字是否小于等于1,如果是,就不是素数,返回False。然后使用for循环从2到数字的平方根(加1)的范围内判断是否能整除,如果是,就不是素数,返回False。最后如果都没有返回False,就说明数字是素数,返回True。 接下来,可以使用filter函数对列表中的所有数字应用这个函数,选出所有不为素数的数字,再通过len函数统计它们的个数。 例如,假设有以下列表: ```python numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] ``` 可以使用以下代码统计所有非素数的个数: ```python non_primes = list(filter(lambda x: not is_prime(x), numbers)) count = len(non_primes) print(count) ``` 这里使用了lambda表达式来简洁地定义一个只有一行的匿名函数来调用is_prime函数,选出所有不为素数的数字,然后将结果转化为列表并统计其长度,最终输出结果为6,即列表中所有非素数的个数。 ### 回答3: 要使用filter函数统计一个列表中的所有非素数,首先需要理解什么是素数素数是指只能被1和自身整除的正整数,如2、3、5、7等。所以非素数就是指除了1和自身能被其他正整数整除的数。可以通过循环判断每个数字是否为素数,但是这样比较繁琐,可以使用filter函数来简化操作。 先来看一下filter函数的用法:filter()函数可以接受一个函数和一个序列作为参数,把传入的函数依次作用于每个元素,然后根据函数返回的结果是True还是False来决定保留还是丢弃该元素。下面给出一个简单的例子: def is_odd(n): return n % 2 == 1 list(filter(is_odd, [1, 2, 3, 4, 5, 6])) 这个例子中,is_odd函数用来判断一个数是否是奇数,filter(is_odd, [1, 2, 3, 4, 5, 6])则是对列表中的每个元素都执行is_odd函数,根据返回结果True或False来决定保留还是丢弃该元素,最后返回一个新的列表[1, 3, 5],即保留了原列表中所有的奇数。 回到统计非素数的问题上,因为我们要统计非素数,所以需要定义一个函数来判断一个数字是否为素数。可以用一个for循环来遍历从2到该数字的所有整数,判断是否存在能够整除该数字的整数,如果存在,则该数字不是素数。代码如下所示: def is_not_prime(n): if n < 2: return True for i in range(2, n): if n % i == 0: return True return False 上面的函数返回True表示该数字不是素数,返回False表示该数字是素数。接下来就可以用filter函数来统计一个列表中所有的非素数了。代码如下所示: nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] non_primes = list(filter(is_not_prime, nums)) print(non_primes) 这段代码中,nums是一个包含了1~10的数字列表,is_not_prime函数用来判断数字是否为素数。然后用filter函数对nums中的每个元素执行is_not_prime函数,生成一个新的列表non_primes,其中存放了所有的非素数。最后用print函数输出non_primes,结果为[1, 4, 6, 8, 9, 10],即1~10中所有的非素数

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值