专栏开篇词
本文是本人博客专栏“数学与数论”的第一篇文章,本专栏将主要撰写计算机科学中数学相关概念,主要大纲围绕AcWing的算法课程开展。本专栏力求用最通俗易懂的语言和精美的辅助插图帮助各位同仁理解其中的核心原理和思想,所以该专栏今后所有文章将全部由本人原创撰写,相关图示、图解在未标明来源的情况下也全部由本人原创绘制。本专栏所有文章遵循CC BY-SA 4.0协议,您可以在标明来源的前提下转载本专栏的任意内容,若有问题或建议,可以直接在文章下方留言,私信很少看的喵,谢谢。
概念
所谓质数,举个例子,比如5就是质数,因为5只有1乘5
7也是质数,因为7只有1乘7。
但10就不是质数,因为10除了1乘10外,还有2乘5。
也就是说除了1和它本身外,没有其他因子了,我们就叫质数。
质数的判定
代码1.0
基本判定代码如下,核心思想是试除法:
#include <stdio.h>
#include <algorithm>
using namespace std;
//输入:待判定的数。
//输出:该数是否为质数,是返回true,否则返回false。
bool is_prime(int n) {
//如果输入的数小于2,直接返回false
if (n < 2) return false;
//从2到n-1开始遍历,看看中间有没有因子,除了以后余数不是0的,如果不是0则说明不是质数
for (int i = 2; i < n; i++)
{
if (n % i == 0) return false;
}
//能跑到这一行,说明遍历完了都没找到,说明这个数就是质数
return true;
}
代码2.0
上面是最古老的版本,还能优化,优化前,我们需要先了解一个定理:
也就是说,d和n/d是成对出现的,这也好理解,比如12可以分成2 x 6或者3 x 4,都是成双成对的,一个出来,另一个自动出来,这么一来,不用扫描到d了,扫到n/d就可以了,也就是根号n,过程如下图所示
优化后的代码如下所示,注意第14行的变化
#include <stdio.h>
#include <algorithm>
using namespace std;
//输入:待判定的数。
//输出:该数是否为质数,是返回true,否则返回false。
bool is_prime(int n) {
//如果输入的数小于2,直接返回false
if (n < 2) return false;
//从2到根号n-1开始遍历,看看中间有没有因子,除了以后余数不是0的,如果不是0则说明不是质数
for (int i = 2; i < n / i; i++)
{
if (n % i == 0) return false;
}
//能来到这一行,说明遍历完了都没找到,说明这个数就是质数
return true;
}
这么一优化,时间复杂度就从n下降到根号n了。
分解质因数
核心思想还是试除法:从小到大尝试所有数,以AcWing 867号题目为例,题目如下
题目做之前的理解工作
质因数,就是质数和因数的结合体(废话),举例说明。
比如12 = 2 x 6,但是这还没完,因为6不是个质数,6还可以分解成2 x 3
所以最终的分解结果应该是12 = 2 x 2 x 3,式子里面2和3都是质数,符合要求了。
接下来我们看一下输入输出样例的解读,如下图所示。
解题思路及代码
依旧以为界,小于
的我们遍历一下,并用一个while循环持续分解,对于大于
的,在代码之后有单独解释,具体请看下面。
代码如下:
//输入:待分解质因数的数。
//输出:分解后的底数和指数。
void divide(int n) {
for (int i = 2; i <= n / i; i++)
{
if (n % i == 0)
{
int s = 0;
while (n % i == 0)
{
n = n / i;
s++;
}
printf("%d %d\n", i, s);
}
}
if (n > 1) printf("%d %d\n", n, 1);
}
int main() {
int n;
//输入待分解质因数的个数
scanf("%d", &n);
//开始逐个获取用户输入的质因数,并分解之
while (n--)
{
int x;
scanf("%d", &x);
divide(x);
}
return 0;
}
注意代码的第41行,这块需要特别说明一下,需要先知道一个定理
n中最多只包含一个大于的质因子
小于的我们已经遍历完了,因此,对于这一个大于
的特殊情况,我们单独处理一下就行,体现在代码的第18行。
筛质数
原理
从2开始,逐个筛除出去,如图所示,我想找2~10内的所有质数。
代码实现1.0
#include <stdio.h>
#include <algorithm>
#include <iostream>
using namespace std;
const int N = 99999;
//primes:存储质数的数组。cnt:质数的个数
int primes[N], cnt;
//st:整个待考察的数范围,程序跑完后,整个st数组被赋予了bool值,若是true则表示当前下标不是质数,反之亦然。
bool st[N];
void get_primes(int n) {
for (int i = 2; i <= n; i++)
{
if (!st[i])
{
primes[cnt++] = n;
}
//从i到n范围内,每个i的倍数都置为true,表示本轮筛除出去了
for (int j = i; j <= n; j += i) st[j] = true;
}
}
int main() {
int n;
cin >> n;
get_primes(n);
cout << cnt << endl;
return 0;
}
代码实现2.0
我们需要先知道一个质数定理:
1~n中,最多有个质数
据此性质,我们可以将第24行的代码放到第20行下面,也就是放到if判断体里面去,得到2.0版本代码。
优化后的2.0版本也叫做埃氏筛法(Sieve of Eratosthenes)