质数
定义:在大于1的整数中,如果只包含1和本身这两个约数,就倍称为质数或素数。
质数的判定——试除法
最暴力的写法:时间复杂度O(n)
bool is_prime(int x)
{
if(x < 2) return false;
for(int i = 2; i < x; i ++ )
if(x % i == 0) return false;
return true;
}
从定义出发,首先看这个数是不是大于2,然后看能否被除1和它本身外其它数整除。
优化
如果 d | n (d能整除n),那么 n/d | n (n除以d也能整除n)。就像 12 | 3,而12 / 3 = 4,则 12 | 4 .
可以看到,质数是成对出现的,因此我们在枚举时,只需要枚举每一对儿中较小的那个,即只需要枚举 d ≤ n/d 这样的数,即 d ≤ √n 这样的数。这样时间复杂度就可以降低到O(√n)(而且一定是√n,而不是最坏),显然是一个质的飞跃。
bool is_prime(int x)
{
if(x < 2) return false;
for(int i = 2; i <= x/i; i ++ )
if(x % i == 0) return false;
return true;
}
注意:for循环中不建议这样写:for(int i = 2; i <= sqrt(x); i ++ ),每次循环都要执行一遍循环条件,但是sqrt()这个函数比较慢;
也不建议写成 i*i <= n,当n比较接近于int的最大值时,存在溢出风险,溢出后i就会变成一个负数;
综上,推荐上面给出的写法,即 i <= x/i。
分解质因数——试除法
思路:从小到大枚举所有数(尝试x的所有因数)
概念:把一个合数分解成若干个质因数的乘积的形式,即求质因数的过程叫做分解质因数。
#include<iostream>
using namespace std;
void divide(int x)
{
for(int i = 2; i <= x/i ; i ++ )
if(x % i == 0) //一定是质数
{
int s = 0;
while(x % i == 0)
{
x /= i;
s ++ ; //s为i的指数
}
printf("%d %d\n",i,s);
}
if(x > 1) printf("%d %d\n",x,1); //大于sqrt(x)的质因子
puts(" ");
}
int main()
{
int n;
cin >> n;
while(n -- )
{
int x;
cin >> x;
divide(x);
}
return 0;
}
时间复杂度:最坏:O(√n),最好:O(logn),实际一般介于二者之间
注意
1.一个很重要的性质:x中最多只包含一个大于sqrt(x)的质数(很好证明,因为如果有两个的话相乘就大于x了,这是矛盾了),所以我们枚举时可以先把小于sqrt(x)的质因子枚举出来。循环结束后,x若是大于1的,说明此时这个x就是那个大于sqrt(x)的质因子。
2.我们可能会疑问,我们要找的是n的所有的质因子,可是我们枚举的是x的所有因数,这其中会包含合数,那么会不会有问题呢?
其实是没有问题的,因为当我们枚举到 i 时,就意味着我们已经把 x 的2~i-1的所有质因子都除干净了(x/=i),也就是说我们此时的x中不含有2 ~ i -1的任何质因子了,如果满足if(x%i == 0),那么说明 i 中也一定不含2 ~ i -1的任何质因子,那么这个 i 一定是个质数。
换句话说,如果i是一个合数的话,合数可以被分解成多个质数相乘,而这多个质数一定比i小且都为x的因子,但比i小的数在之前就已经被除掉了,所以i一定是一个质数。