一. 质数的筛选
- 朴素筛选 : 试除法
若一个正整数N为合数,则存在一个能 整除 N 的 T , 其中 2 <= T <= sqrt(N)
根据上述命题,我们只需要扫描2~sqrt(N)之间的所有整数,依次检查它们能否整除N.若都不能整除,则N是质数,否则N是合数。试除法的时间复杂度为0(sqrt(N))。当然,我们需要特判0和1这两个整数,它们既不是质数,也不是合数。
代码
int prime[N];
int cnt;
bool is_prime(int n){
if(n < 2) return false;
for(int i = 2; i <= sqrt(n); i++)
if(n % i == 0) return false;
return true;
}
void get_primes(int n){
for(int i = 2; i <= n; i++){
if(is_prime(i)) prime[++cnt] = i;
}
}
- Eratosthenes筛法
Eratosthenes筛法基于这样的想法:任意整数x的倍数2x,3x,. 都不是质数。根据质数的定义,上述命题显然成立。
我们可以从2开始,由小到大扫描每个数x,把它的倍数2x,3x,…,[ N/x ]*x (下取整) 标记为合数。当扫描到一一个数时, 若它尚未被标记,则它不能被2~x- 1之间的任何数整除,该数就是质数。
代码
int prime[N];
int cnt;
bool vis[N];
void get_primes(int n){
for(int i = 2; i <= n; i++){
if(vis[i]) continue;
prime[++cnt] = i;
for(int j = i; j <= n / i; j++)
vis[i * j] = true;
}
}
Eratosthenes筛法的时间复杂度为O(N loglogN)。该算法实现简单,效率已经非常接近于线性,是算法竞赛中最常用的质数筛法。
- 线性筛法
即使在优化后(从x^2开始),Eratosthenes 筛法仍然会重复标记合数。例如12既会被2又会被3标记,在标记2的倍数时,12=6 * 2,在标记3的倍数时,12=4* 3. 其根本原因是我们没有确定出唯一的产生12的方式。
线性筛法通过“从大到小累积质因子”的方式标记每个合数,即让12只有3 * 2 * 2一种产生方式。
代码
int prime[N];
int cnt;
bool vis[N];
void get_primes(int n){
for(int i = 2; i <= n; i++){
if(!vis[i]) prime[++cnt] = i;
for(int j = 1; prime[j] * i <= n; j++){
vis[prime[j] * i] = true;
if(i % prime[j] == 0) break;
}
}
}
二. 质因数分解
- 试除法
结合质数判定的“试除法”和质数筛选的“Eratosthenes筛法”,我们可以扫描2~sqrt(n)的每个数d,若d能整除N,则从N中除掉所有的因子d,同时累计除去的d的个数。
因为一个合数的因子定在扫描到这个合数之前就从 N中被除掉了,所以在上述过程中能整除N的一定是质数。 最终就得到了质因数分解的结果,易知时间复杂度为0(sqrt(N))。
特别地,若N没有被任何2~vN的数整除,则N是质数,无需分解。
代码
void divide(int n){
int m = 0;
for(int i = 2; i <= sqrt(n); i++){
if(n % i == 0){ //i是质数
p[++m] = i;
c[m] = 0;
while( n % i == 0){ //除掉所有的i
n /= i;
c[m]++;
}
}
}
if(n > 1) //n 是质数
p[++m] = n;
c[m] = 1;
for(int i = 1; i <= m; i++){
cout << p[i] << "^" << c[i] << endl;
}
}