质数
试除法判定质数
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;
}
质数的筛选
给定一个整数N,求出1到N间的所有质数,称为质数的筛选问题
最基础埃氏筛
埃氏筛基于思想:任意整数的倍数都不是质数。因此可以从2开始,从小到大扫描每个数x,把它的倍数都打上标记。若扫描到一个数x时,这个数没有被标记,那么说明这个数是质数(从2到x−1没有数能整除x)。
void primes(int n)
{
memset(st, false, sizeof st); //合数标记
for (int i = 2; i <= n; i++)
{
if (st[i])
continue;
for (int j = 2; j <= n / i; j++)
st[i * j] = true;
}
}
优化埃氏筛
对于基础埃氏筛,一些数会重复打上标记,例如2和3都会把6打上标记。实际上,小于x2的x的倍数在扫描更小的数时就已经被标记过了。想不通的可以就看一下6这个数,6是3的2倍因此会在扫描3时又被标记,3的2倍等价于2的3倍,而这在扫描2时就被标记了。时间复杂度是O(NloglogN)
void primes(int n)
{
memset(st, false, sizeof st); //合数标记
for (int i = 2; i <= n; i++)
{
if (st[i])
continue;
for (int j = i; j <= n / i; j++)
st[i * j] = true;
}
}
线性筛
即使是优化的埃氏筛还是会重复标记一些数,例如12还是会被2和3重复标记。线性筛的思想是让每个合数只被它的最小质因子筛一次。设数组v记录每个数的最小质因子,我们按照一下步骤维护v:
1.依次考虑2~n的每一个数
2.若v[i]=i,说明i是质数,把它保存下来
3.扫描不大于v[i]的每个质数p,令v[i×p]=p。也就是在i的基础上累积一个质因子p。因为p≤v[i],所以p就是合数i×p的最小质因子。
每个合数只会被最小质因子筛一次,时间复杂度是线性O(N)
// v[i]!=i就说明i是合数,知道这个好像更好理解一点
void primes(int n)
{
memset(v, 0, sizeof v); // v[i]记录数字i的最小质因子
m = 0; // m记录质数个数
for (int i = 2; i <= n; i++)
{
if (v[i] == 0) // i是质数
{
v[i] = i;
prime[++m] = i;
}
for (int j = 1; j <= m; j++)
{
//若i有比prime[j]更小的质因子,或超出n的范围,则停止循环
if (prime[j] > v[i] || prime[j] > n / i)
break;
// prime[j]是合数i*prime[j]的最小质因子
v[i * prime[j]] = prime[j];
}
}
}
质因数分解
算术基本定理
任何一个大于1的正整数都能唯一分解成有限个质数的乘积,可写作:N=p1c1p2c2⋯pmcm
其中ci都是正整数,pi都是质数,且p1<p2<⋯<pm
试除法分解质因数
扫描2~√n的每个数d,若d能整除N,则从N中除掉所有的因子d,同时记录除去的d的个数
特别地,若没有2~√n的数能整除n,说明n是质数无需分解
void divide(int n)
{
m = 0;
for (int i = 2; i <= n / i; i++)
{
if (n % i == 0)
{
p[++m] = i, c[m] = 0;
while (n % i == 0)
n /= i, c[m]++;
}
}
if (n > 1) //还除剩一个质数
p[++m] = n, c[m] = 1;
}