线性时间内筛素数和欧拉函数

来源:互联网 发布:淘宝巴黎心店卖假货 编辑:程序博客网 时间:2024/06/05 16:19

学习了下线性时间筛素数和求欧拉函数

大部分类容转自点击打开链接 与 点击打开链接 我为了便于自己以后利用,稍作了整理

欧拉函数是指:对于一个正整数n,小于n且和n互质的正整数(包括1)的个数,记作φ(n) 。

首先给出公式:φ(x)=x*(1-1/p1)*(1-1/p2)*(1-1/p3)*(1-1/p4)…..(1-1/pn),其中p1, p2……pn为x的所有质因数,x是不为0的整数。φ(1)=1

证明过程:只给出关键地方

因为任意正整数都可以唯一表示成如下形式:

                     k=p1^a1*p2^a2*……*pi^ai;(即分解质因数形式)

可以推出:φ(k)= (p1 ^ k1 - p1 ^ (k1 - 1)) * (p2 ^ k2 - p2 ^ (k2 - 1)) * ... *(pn ^ kn - pn ^ (kn - 1))           -----------------------------      (1)
               =k*(1-1/p1)*(1-1/p2)....(1-1/pk)

得到(1)的几个关键的地方:

1.当n为素数时,φ(n) = n - 1。因为每个比n小的正整数都和n互素。当n为素数p的k次方时,φ(n) = p ^ k - p ^ (k - 1)。因为在1到n之间的正整数只有p的倍数和n不互素,这样的数有(p ^ k / p)个。

2. 如果m和n互素,即GCD(m, n) = 1,那么φ(m * n) =φ(m) *φ(n)。

性质:

1.设a为N的质因数,

   若(N % a == 0 && (N / a) % a == 0) 则有φ(N)=φ(N / a) * a;

   若(N % a == 0 && (N / a) % a != 0) 则有:φ(N) = φ(N / a) * (a - 1)。

2.当n为奇数时,φ(2n)=φ(n)


然后这里有一种线性时间内筛素数并顺便求欧拉函数的方法

算法的论文:点击打开链接

这个算法的核心思想是:每一个合数可以被唯一地表示成它的一个最小质因子和另外一个数的乘积。证明略。

伪代码:

S = {2, ..., n}for (p = 2; p*p <= n; p = next(S, p)):    print p    for (i = p; i*p <= n; i = next(S, i)):        for (j = i*p; j <= n; j = j*p):            S = S - {j}next(S, i) is a function defined only for i ∈ S such that there is an integer larger than i in S; it yields the next larger integer in S. 

通俗的说,就是对于每一个质数p,枚举i,删除i*p^1, i*p^2, i*p^3, ...

这个是筛素数的一个很原始的方案,也就是把质数的所有倍数删掉。看起来很暴力,但是很容易证明它的正确性,更容易证明它的时间复杂度是O(n)的:因为每一个元素最多被删除一次,并且没有新的元素插入S中!

但是这个复杂度是不考虑删除集合元素和next操作的代价的,如果真的如此维护动态集合,那么复杂度还得乘上O(logn),并且得借助平衡树实现。

因此就有了一些改进的方法,把这个伪代码拐了个弯,免去了维护动态集合。

UPDATE: 其实由于这个集合的特殊性(只删不增,并且只获取下一个元素),所以还是可以直接维护这个集合的(比如用布尔记录+指针扫描等等),只是看起来不怎么好看,写起来不怎么好写而已。

代码

下面代码就是带有计算欧拉函数的线性筛素数。代码原型的起源已经无从考证,可以作出一个合理的揣测,是某位搞OI或者ACM/ICPC的神牛第一次写出来的。


//**********************************************************************const int MAX_N = 1e5 + 7;__int64 primes, prime[MAX_N], phi[MAX_N];bool com[MAX_N];int N;void get_prime_phi(){    memset( com, false, sizeof( com ) );    primes = 0;    phi[1] = 1;    for (int i = 2; i <= N; ++i){        if (!com[i]) {            prime[primes++] = i;            phi[i] = i-1;        }        for (int j = 0; j < primes && i*prime[j] <= N; ++j){            com[i*prime[j]] = true;            if (i % prime[j])                phi[i*prime[j]] = phi[i]*(prime[j]-1);            else {                phi[i*prime[j]] = phi[i]*prime[j];                break;            }        }    }}//__int64 get_phi( __int64 x )//单独求欧拉函数//{//    __int64 i, res=x;//    for (i = 2; i < (int)sqrt( x * 1.0 ) + 1; i++)//        if(x%i==0) {//            res = res / i * (i - 1);//            while (x % i == 0) x /= i; // 保证i一定是素数//    }//    if (x > 1) res = res / x * (x - 1);//    return res;//}//**********************************************************************


最难理解的是这句话:

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

要理解这句话,(顺便不严谨地)证明这个算法的时间复杂度和正确性,要从下面两个方面:

  • 每个数至少被访问一次
  • 每个数至多被访问一次

每个数至少被访问一次

对于质数,一定会在i的循环中访问到,并确定为质数。

对于合数,因为每一个合数都可以表示成它最小的质因数另一个数的乘积,而我们枚举了所有的另一个数(也就是i),所以它一定会被它的最小质因数筛掉。

每个数至多被访问一次

对于质数,不可能在j的循环中被访问到,因此仅会在i的循环中被访问到恰好一次。

对于合数,对于i = i1 = p * a,因为在i1 % prime[j1] == 0时break,所以不可能出现一个数x = i1 * prime[k] = p * a * prime[k] (k > j1)i = i1, j = k的时候被筛掉一次,又在i = a * prime[k]的时候被p给筛掉的情况。

证毕

综上所述,每个数被访问一次且仅访问一次!因此整个算法的复杂度是O(n)的。




0 0
原创粉丝点击