数论及其应用——素数问题

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数论及其应用——素数问题相关的知识,希望对你有一定的参考价值。

  数学是科学的女王,数论是数学的女王——高斯。
  然后我再狗尾续个貂——素数是数论的女王。

  谈及素数,可以牵扯出很多数学史上的美谈,例如前几天在知乎上看到关于“除去酒色,人类还怎么享受生活”的问题,在一个回答中就举个几个大科学家的例子,其中提到某个钟爱素数的数学家,选择再每月的素数天和妻子同居,一个月刚开始还好,但越到月末素数间隔变大,同居的日子也就变少。

  在素数这块小地方,有很多著名的猜想,能证出来一些真的是非常非常的厉害,因为当今世界流传着这样一个传说,集齐七大世界数学难题,便可以召唤神龙,帮助你实现一个愿望。
  扯远了,这里先介绍几个关于素数的猜想。
  
  孪生素数猜想:存在无穷多的形如p,p+2的素数对。
  1966年陈景润用复杂的筛法证明了存在无穷多对孪生素数,现在寻找孪生素数成为了一种趣味竞赛,目前最大的孪生素数对是33218925.2^169690 +1 , 33218925.2^169690 -1 。华人数学家张益唐在这方面有很杰出的成就。

  哥德巴赫猜想:每个大于2的正偶数可以写成两个素数的和。
  这个猜想是1742年哥德巴赫写给欧拉的信中给出,目前已经验证了所有小于4 * 10^14的偶数满足这一猜想。

  n^2 + 1猜想:存在无穷多个形如n^2 + 1的素数,其中n是正整数。 

   人们不仅对素数的形式着迷,也热衷于探索素数的分布规律。这里我们引出素数定理——设π(x)为小于整数x的素数个数,那么随着x的增大,有π(x) = x/lnx。由于时间原因,这里暂且不探讨证明过程。

  那么基于这个定理,我们来解决一个实际的问题。(Problem source : nufu 117)

技术分享
  针对这个问题如此大的数据量,我们显然无法直接运算,那么就要用到我们的素数定理里。但是这里需要注意的是,这里要求的是素数个数的位数,并且这里的n表示的是10^n,其含义是与定理中的x有区别的。
  另外,求一个整数x的位数,有公式lg(x) + 1,那么lg(10^n/ln(10^n)) + 1即是本题目所求,在进行简单的化简、编程实现即可。
 
  参考代码如下。

 #include<iostream>
#include<math.h>
using namespace std;

int main()
{
    int n;
    double e = 2.71828;
    while(cin >> n)
    {
          double m = (n - log10(n)-log10(log(10)));
          cout << int(m) + 1 << endl;
    }
}

 



  除了素数的分布规律,对于判定一个数是否是素数也是一个最基础的问题。
  对于素数的判定,我们最先想到的是根据其定义进行暴力穷举。即给定数字n,我们遍历一下[1,n]的整数,判断有可以整除的因子。
  但是我们发现,n的因子是成对出现的,即n = i * j,当我们遍历到i的时候,其实是找到了一个因子对——i,j,那么这样我们遍历j的时候,再次访问了因子对——i,j,这就造成了时间上的浪费。从数轴上来看,这些成对的因子是“成对”的,而这个对称点显然是sqrt(n)。因此我们可以对上面判断素数的方法进行优化,遍历[1,sqrt(n)]。
  但是还有更高效的算法——埃拉托色尼筛法。它不需要证明,很易懂。就是我们假定[1,N]上的所有整数是素数,然后从2开始往后遍历,如果当前的i是一个素数,那么进入循环,循环中把[1,N]中所有i的倍数筛选出来,标记为合数。对比前两种方法,这种方法显然高效了许多。
  以上的方法处理数据的上限是依次递增的,但如果一个数据练第三种筛法都处理不了呢?那我们就灵活的结合第二种普通筛法和第三种高效筛法。假设给定了一个很大的整数n,我们遍历[1,sqrt(n)]间的整数,这里用到的是第二种普通筛法。但是我们只需要遍历这个区间的素数,因为如果是合数的话,它一定有一个更小的素因子,这在之前就一定会遍历到,因此造成了重复遍历引起了时间上的浪费。而如何找到这个区间上的素数,就靠搞笑的埃拉托色尼筛法了。
  只要充分理解了筛法本身的原理,编程实现上就是简单的模拟,很容易实现。

    那么我们来结合一个题目具体实现以下代码。(Problem source : nefu 109)
   技术分享
    可以看到这就是我们提及到的处理数据非常大的题目,此时就需要两种筛法结合起来。
  参考代码如下。

  #include<stdio.h>
#include<iostream>
#include<cmath>
#include<cstring>
const int N = 50001;
using namespace std;
bool isprime[N];
int prime[N],nprime;
void doprime()  //埃拉托色尼筛选法
{
     long long i , j;
     nprime = 0;
     memset(isprime,true,sizeof(isprime));
     isprime[1] = 0;
       for(i = 2;i < N;i++)
       {
              if(isprime[i])
              {
                    prime[++nprime] = i;
                    for(j = i+i;j < N ;j += i)
                    {
                        prime[j] = false;
                    }
              }
       }
}
bool isp(int n) //基本筛法
{
     int i , k = (int)sqrt(double(n));
        for(i = 1;prime[i] <= k;i++)
             if(n%prime[i] == 0)
                return 0;
            return 1;
}
int main()
{
     doprime();
     long long n;
     while(cin >> n)
     {
          if(n == 1)
          {
              cout << "NO"<<endl;
              continue;
          }
        if(isp(n))  cout << "YES" << endl;
        else        cout << "NO" << endl;

     }
     return 0;
}

 


 

 

  从素数的角度来分析整个数域,数学家发现了很多规律,通过归纳法我们很容易得知n可以表示成素数之积,即n = p1*p2*p3……pn,这里的pi是素数。下面我们给出算术基本定理。

  定理:每个大于1的正整数n,都可以被唯一地写成素数的乘积:n = p1^a1 * p2^a2 * …… * pk^ak。基于这个式子,我们可以得到很多有用的性质,在文章的后面我们将依次得介绍。

  性质1:n!的素因子分解中的素数p的幂为:[n/p] + [n/p^2] + [n/p^3]……。在这里由于时间原因和笔者的能力原因,我们暂且不提它的详尽证明。而是通过先通过实践来学会应用它。

  那么让我们来看一道需要应用到这条性质的问题。(Problem source : nefu 118)

 技术分享

我们来看这个问题,显然我们直接求出n!的值而后%10计算的暴力方法是不合理的,我们要灵巧的利用我们刚才得知的性质。因为这条性质中和这道题目一样,都包含n!。我们从素数基本定理的角度来看n!这个数,它可以写成多个素数的乘积的形式,即n! = p1^a1 * p2^a2 * p3^a3…… * pm ^ am的形式。那么此时我们想要知道这个数末尾有多少个零,转化一下,就是通过将n!写成乘积的形式,然后找到10这个因子的幂次,而10又可以继续拆分成两个素数——2和5,即n! = 2^a * 5^b *……,min(a,b)就这这道题目的答案。我们很容易看到,n!这个数字,拆成素数相乘的形式,2的幂次肯定大于5的幂次,因此这里我们只要找到5的幂次,即可作为这道题的答案。

  有了这层数理的分析,我们就可以应用到上面的性质了。而在编程实现上,也只需简单的设计循环节来求解即可。

  参考代码如下。

  #include<cstdlib>

#include<iostream>

using namespace std;

int main()

{

    int n,m,t,sum,five;

    cin>>n;

    while(n--)

    {

        cin>>m;

        t = m;

        five = 5;

        sum = 0;

        while(five <= t)

        {

            sum = sum + t/five;

            five = five * 5;

        }

         cout << sum << endl;

    }

    return 0;

}

 参考系:《数论及其应用》 陈宇

                                                                                                                                                                                    ——<未完>

以上是关于数论及其应用——素数问题的主要内容,如果未能解决你的问题,请参考以下文章

数论及其应用——同余式定理

三 数论的编程实验

数论-素数问题整理

数论部分第一节:素数与素性测试详解

数论——素数

初等数论-Base-1(筛法求素数,欧拉函数,欧几里得算法)