线性时间 筛素数,求前n个数的欧拉函数值,求前n个数的约数个数

本文介绍了利用积性函数优化算法,具体包括线性时间筛素数、线性时间求前n个数的欧拉函数值和线性时间求前n个数的约数个数。详细解释了积性函数的概念,如欧拉φ函数、默比乌斯函数等,并展示了如何通过线性筛素数算法优化欧拉函数和约数个数的计算。此外,还提供了欧拉函数和约数个数的代码实现。

转自 http://www.cnblogs.com/suno/archive/2008/02/04/1064368.html

利用积性函数的优化.

这个文章主要介绍了3算法

1线性时间筛素数

2线性时间求前n个数的欧拉函数值

3线性时间求前n个数的约数个数

 

一、   首先介绍下积性函数。

 

下面是wiki的条目:

 

在非数论的领域,积性函数指有对于任何a,b都有性质f(ab)=f(a)f(b)的函数。

 

在数论中的积性函数。对于正整数n的一个算术函数f(n),当中f(1)=1且当a,b互质,f(ab)=f(a)f(b),在数论上就称它为积性函数。

若某算术函数f(n)符合f(1)=1,且就算a,b不互质,f(ab)=f(a)f(b),称它为完全积性的。

 

例子

φ(n) -欧拉φ函数,计算与n互质的正整数之数目

μ(n) -默比乌斯函数,关于非平方数的质因子数目

gcd(n,k) -最大公因子,当k固定的情况

d(n) -n的正因子数目

σ(n) -n的所有正因子之和

σk(n): 因子函数,n的所有正因子的k次幂之和,当中k可为任何复数。在特例中有:

σ0(n) = d(n) 及

σ1(n) = σ(n)

1(n) -不变的函数,定义为 1(n)=1 (完全积性)

Id(n) -单位函数,定义为 Id(n)=n (完全积性)

Idk(n) -幂函数,对于任何复数、实数k,定义为Idk(n) = nk (完全积性)

Id0(n) = 1(n) 及

Id1(n) = Id(n)

ε(n) -定义为:若n = 1,ε(n)=1;若n > 1,ε(n)=0。有时称为“对于狄利克雷回旋的乘法单位”(完全积性)

(n/p) -勒让德符号,p是固定质数(完全积性)

λ(n) -刘维尔函数,关于能整除n的质因子的数目

γ(n),定义为γ(n)=(-1)ω(n),在此加性函数ω(n)是不同能整除n的质数的数目

所有狄利克雷特性均是完全积性的

 

 

二、再介绍下线性筛素数方法

 

bool notp[mr];//素数判定

__int64 pr[670000],pn,ans;//pr存放素数,pn当前素数个数。

 

void getprime()

{

    pn=0;

    memset(notp,0,sizeof(notp));

    for(int i=2;i<mr;i++)

    {

        if(!notp[i])pr[pn++]=i;

        for(int j=0;j<pn && pr[j]*i<mr;j++)

        {

            notp[pr[j]*i]=1;

            if(i%pr[j]==0)break;

        }

    }

}

 

利用了每个合数必有一个最小素因子

每个合数仅被它的最小素因子筛去正好一次。所以为线性时间。

代码中体现在:

if(i%pr[j]==0)break;

pr数组中的素数是递增的,当i能整除pr[j],那么i*pr[j+1]这个合数肯定被pr[j]乘以某个数筛掉。

因为i中含有pr[j],pr[j]比pr[j+1]小。接下去的素数同理。所以不用筛下去了。

在满足i%pr[j]==0这个条件之前以及第一次满足改条件时,pr[j]必定是pr[j]*i的最小因子。

 

 

三、结合线性筛素数算法的优化算法

基于这个线性筛素数算法,我们可以很容易地得到某个数的最小素因子。

因为当i%pr[j]!=0的时候,最小素因子pr[j]与i互质,满足积性函数的条件,可以直接得到f(i*pr[j])=f(i)*f(pr[j]).

不过当i%pr[j]==0时我们必须根据该积性函数本身的特性进行计算.或者在筛的同时保存并递推些附加信息.总之要O(1)求得f(i*pr[j])及完成递推附加信息.

 

下面的两个例子是欧拉函数phi和约数个数.这两个是最常用和最有优化价值的。

利用上面的性质都可以很容易地把前n个用O(n)时间推出来.

当然,利用这个性质还可以对其他积性函数进行优化,这里仅介绍两个常用和有优化价值的。

 

1)欧拉函数(phi)

传统的算法:

对于某素数p且p|n(n能整除p)

if( (n/p) % i == 0 ) phi(n)=phi(n/p)*i;

else phi(n)=phi(n/p)*(i-1);

 

这个传统算法的性质正好用在筛素数算法中.

p为n的最小素因子,当n/p包含该因子p,则phi(n)=phi(n/p)*i;否则phi(n)=phi(n/p)*(i-1);

p即pr[j], n/p即i, n即i*pr[j]了.

 

 

2)约数个数(divnum)

约数不能像phi那么自然,但还是有不错的方法.

约数个数有个性质

divnum(n)=(e1+1)*(e2+1)...(ei表示n的第i个质因数的个数.)

传统方法就是对每个数分解质因数,获得各因数个数再用上式.

 

开一个空间e[i]表示最小素因子的次数

这次说直接点:

筛到i 第j个素数

 

对于divnum

如果i|pr[j] 那么 divnum[i*pr[j]]=divsum[i]/(e[i]+1)*(e[i]+2) //最小素因子次数加1

否则 divnum[i*pr[j]]=divnum[i]*divnum[pr[j]] //满足积性函数条件

 

对于e

如果i|pr[j]  e[i*pr[j]]=e[i]+1; //最小素因子次数加1

否则 e[i*pr[j]]=1; //pr[j]为1次

 

Eular函数代码如下:

#include <cstdlib>
#include <cstdio>
#include <algorithm>
#define MAXN 1000000
using namespace std;

int p[MAXN+5], pri[1000000], idx = -1;
int phi[MAXN+5];

void GetPrime()
{
    for (int i = 2; i <= MAXN; ++i) {
        if (!p[i]) { // 说明i是一个素数
            pri[++idx] = i;
        }
        for (int j = 0; j <= idx && pri[j]*i <= MAXN; ++j) {// 遍历所有的素因子
            p[pri[j]*i] = 1;
            if (i % pri[j] == 0) {  // 如果i能够整除pri[j]那么i*pri[j+1]就一定被pri[j]数整除 
                break;
            }
        }
    }
    printf("idx = %d\n", idx);
    for (int i = 0; i <= 100; ++i) {
        printf("%d  ", pri[i]);
    }
    puts("");
}

// 现在计算phi[N=p^e]的取值,由于p是一个素数,我们可以知道在 1 ~ p^e 中的数有 1*p, 2*p, 3*p ... [p^(e-1)]*p 
// 一共有p^(e-1)个不与p^e互质,所以phi[N] = p^e - p^(e-1) = [p^(e-1)]*(p-1) = << phi[N/p]*p >>
// N = p1^e1 * p2^e2 *.... 中 如果 e1 = 1 的话,那么我们由欧拉函数积性直接等于 phi[i/p1] * phi[p1]
// 如果 e1 != 1,那么我们就直接利用上面尖括号包括的式子得到 phi[N] = phi[i/p1] * p1

void Eular()
{
    for (int i = 2; i <= MAXN; ++i) {
        if (!p[i]) {
            phi[i] = i - 1;  // 如果是一个素数,欧拉函数就是 i-1
            continue;
        }
        for (int j = 0; pri[j] * pri[j] <= i; ++j) {  // 进入的 i 一定是一个合数
            if (i % pri[j] == 0) { // 如果pri[j]是i的一个素因子 
                if (i / pri[j] % pri[j] == 0) { // 并且这个素因子还有两个以上的指数
                    phi[i] = pri[j] * phi[i/pri[j]]; //  那么就直接乘以这个素因子
                }
                else {
                    phi[i] = phi[pri[j]] * phi[i/pri[j]];
                }
                break;
            }
        }
        printf("phi[%d] = %d\n", i, phi[i]);
        getchar();
    }
    return;
}

int main()
{
    GetPrime();
    Eular();
//    system("pause");
    return 0;    
}

 

 

转载于:https://www.cnblogs.com/Lyush/archive/2012/08/06/2625613.html

<think>好的,用户想了解如何用快速幂算法计算质数的个数。首先,我需要回忆快速幂算法的作用。快速幂主要用于高效计算大指数的幂,比如$a^n$,其时间复杂度是$O(\log n)$,比普通的$O(n)$方法快很多。不过,质数统计通常涉及选法,比如埃拉托斯特尼法(埃氏)或欧拉,这些方法的时间复杂度是线性的或者接近线性的。 用户的问题可能有些混淆,因为快速幂本身并不直接用于统计质数。不过,可能存在某些特定场景或优化方法中结合了快速幂的思想。例如,在Miller-Rabin素数测试中,快速幂被用来进行模幂运算,以加速素数的概率判定。但统计质数个数通常还是依赖法。 接下来,我需要查阅提供的引用资料。引用[3]提到了质因子、质数、约数、余数、快速幂等,其中快速幂可能用于素数测试中的模运算。引用[4]中的LeetCode问题使用快速幂来处理大指数的计算,可能类似的思路可以应用到某些质数统计的优化中。 可能的思路是:如果用户的问题是关于使用快速幂来优化某些步骤,比如在判定质数时使用Miller-Rabin测试,而该测试中的模幂运算需要快速幂。但统计质数个数的主流方法仍然是法,而快速幂可能在其中作为辅助工具。 需要指出的是,快速幂算法本身并不直接用于统计质数,但可以辅助某些素数判定算法。例如,Miller-Rabin测试中的快速模幂计算。因此,正确的回答应该澄清这一点,并说明两者的结合方式。 然后,我需要给出一个可能的实现方法,比如结合埃氏和快速幂进行优化,或者展示在Miller-Rabin测试中使用快速幂来加速单个质数的判定,进而统计质数数量。但需注意,对于大规模质数统计,法更高效。 最后,生成相关问题,帮助用户进一步理解相关算法之间的关系和应用。</think>快速幂算法本身并不直接用于统计质数个数,但可以辅助优化质数判定的过程。结合快速幂的典型应用场景是在**Miller-Rabin素性测试**中加速模幂运算,从而高效判定单个数字是否为质数。以下是具体实现思路和方法: --- ### 一、快速幂与质数判定的结合原理 1. **Miller-Rabin素性测试** 通过选取多个基值对数字$n$进行测试,利用快速幂计算$a^{d \cdot 2^s} \mod n$的值,若结果不满足特定条件则判定$n$为合数。成功通过多轮测试的数可视为质数(概率正确)[^3]。 2. **快速幂的核心作用** 在模幂运算$a^b \mod n$中,快速幂将时间复杂度从$O(b)$优化到$O(\log b)$,极大提升了判定效率[^2]。 --- ### 二、统计质数的实现步骤(结合快速幂) 1. **选候选数范围** 使用传统法(如埃氏)缩小范围,再对选后的候选数应用Miller-Rabin测试。 2. **Miller-Rabin测试实现** ```python def is_prime(n, k=5): if n <= 1: return False if n <= 3: return True # 将n-1分解为d*2^s d = n - 1 s = 0 while d % 2 == 0: d //= 2 s += 1 # 进行k轮测试 for _ in range(k): a = random.randint(2, min(n-2, 1 << 20)) x = pow(a, d, n) # 快速幂计算 if x == 1 or x == n-1: continue for __ in range(s-1): x = pow(x, 2, n) if x == n-1: break else: return False return True ``` 3. **统计质数个数** ```python def count_primes(n): if n < 2: return 0 sieve = [True] * (n+1) sieve[0] = sieve[1] = False for i in range(2, int(n**0.5)+1): if sieve[i]: sieve[i*i:n+1:i] = [False] * len(sieve[i*i:n+1:i]) candidates = [i for i, is_p in enumerate(sieve) if is_p] # 对选后的候选数进行二次验证 count = 0 for num in candidates: if is_prime(num): count += 1 return count ``` --- ### 三、性能分析 - **时间复杂度**:埃氏为$O(n \log \log n)$,快速幂辅助的Miller-Rabin测试为$O(k \log^3 n)$,综合效率仍低于纯法,但适合超大数范围的分段统计。 - **空间复杂度**:埃氏需要$O(n)$空间,可通过分段优化。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值