简单数论——质数
质数
在 >1 的整数中,如果只包含1和本身两个约数,这个数就被称为质数/素数。
质数的判定——试除法
暴力枚举 \(O(n)\)
bool is_prime(int n)
{
if(n < 2) return ;
for(int i = 2; i < n; i++)
if(n % i == 0) return false;
return true;
}
简单优化 \(O(\sqrt{n})\)
对任意的d能整除n( d|n ), 显然n除上d也能整除n ( \(\frac{n}{d}\) | n )
如n = 12,3是12的约数,4也是12的约数; 2是12的约数,6也是12的约数
所有的约数都是成对出现的,在枚举时可以只枚举每一对中较小的那个,即\(d \leq \frac{n}{d}\)的部分,即\(d \leq \sqrt{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;
}
分解质因数 \(O(\log_ax\) ~ \(\sqrt{n})\)
hihoCoder 1750
如n = \(2^{k}\),可能他的时间复杂度是O(1)的
质因数:在数论里是指能整除给定正整数的质数。每个合数都可以写成几个质数相乘的形式,如6=2*3,8=\(2^{3}\),这几个质数就叫做这个合数的质因数。
很轻易就可以得出n中最多只包含一个大于\(O(\sqrt{n})\)的质因子,所以只需要把所有 < \(O(\sqrt{n})\) 的质因子枚举出来。
void divide(int x)
{
for (int i = 2; i <= x / i; i ++ )
if (x % i == 0) // 这步成立,i一定是质数
{
int s = 0;
while (x % i == 0) x /= i, s ++ ;
cout << i << ' ' << s << endl;
}
if (x > 1) cout << x << ' ' << 1 << endl;
cout << endl;
}
筛选质数
hihoCoder 1295
如
从前往后看,把每一个数的所有倍数全部删掉
第一个数是2,把4,5,8,10,12全部删掉
第二个数是3,把6,9,12全部删掉
第三个数是4,把8,12删掉
如此这么筛选过一遍之后,所有剩下的数就是质数
对于任意一个数x而言,他如果没被删除,就说明p不是2p-1的任意一个数的倍数,即2p-1不存在任何一个p的约数,所以p是一个质数。
朴素做法 O(n\(\ln n\))
当i=2时第一重循环循环了\(\frac{n}{2}\),当n=3时循环了\(\frac{n}{3}\),当n=4时循环了\(\frac{n}{4}\)次,以此类推可知
O(n) = \(\frac{n}{2}\) + \(\frac{n}{3}\) + ... + \(\frac{n}{n}\)
= n(\(\frac{1}{2}\) + \(\frac{1}{3}\) + ... + \(\frac{1}{n}\))
\(\frac{1}{2}\) + \(\frac{1}{3}\) + ... + \(\frac{1}{n}\)是一个调和级数,当n->\({\infty}\)时,这个调和级数\(\approx\ln n\) + c
c是一个欧拉常数,约等于0.58
简单优化 埃式筛法
实际上并不需要将所有数的倍数全部删掉,只把质数的倍数删掉就可以了
假设剩下一个p,我们并不需要将2p-1全部枚举一遍来判断p是不是质数,只需要把2p-1的质数判断一遍就可以了
2~p-1的所有质数只要不是p的约数,那么p就是一个质因数
当一个数不是质数我们就不需要筛掉他所有倍数,只有他是质数才会筛掉他的倍数
质数定理:1~n当中有\(\frac{n}{\ln n}\)个质数
O(n) = O(n\(\ln n\) \ \(\ln n\)) \(\approx\) O(n)
实际复杂度为O(n \(\log_a\ log_a x\))
当n=\(2^32\)时,\(\log_2 32\) = 32, \(\log_2 \log_2 32\) = 5,所以\(\log_a\ log_a x\)是很小的,与O(n)是一个级别的
int primes[N], cnt; // primes[]存储所有素数
bool st[N]; // st[x]存储x是否被筛掉
void get_primes(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (st[i]) continue;
primes[cnt ++ ] = i;
for (int j = i + i; j <= n; j += i)
st[j] = true;
}
}
线性筛法
在\(10^7\)以上线性筛法大约比埃式筛法快一倍
核心思路:每一个数n只会被他的最小质因子筛掉
每一个数只有一个最小质因子,即每个数只会被筛一次,所以他是线性的
int primes[N], cnt; // primes[]存储所有素数
bool st[N]; // st[x]存储x是否被筛掉
void get_primes(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (!st[i]) primes[cnt ++ ] = i;
for (int j = 0; primes[j] <= n / i; j ++ )
{
st[primes[j] * i] = true;
if (i % primes[j] == 0) break; // 这句话执行证明primes[j]一定是i的最小质因子,因为j是从小到大进行的枚举
}
}
}
j是从小到大枚举的所有质数,每一次把当前质数和i的乘积筛掉
i%pj == 0
说明primes[j]一定是i的最小质因子,pj也一定是pji的最小质因子
i%pj != 0
由于我们是从小到大枚举,没有枚举到i的最小质因子
说明pj一定小于i的所有质因子,pj也一定是pji的最小质因子
一共有两种可能:
-
i % primes[j] == 0
- 说明primes[j]一定是i的最小质因子,pj也一定是pj*i的最小质因子
-
i % primes[j] != 0
- 由于我们是从小到大枚举,没有枚举到i的最小质因子
- 说明pj一定小于i的所有质因子,pj也一定是pj*i的最小质因子
不管什么情况,一定有primes[j]是primes[j]*i的最小质因子
任何合数一定会被筛掉,对于任意合数x:
- 假设primes[j]是x的最小质因子,当i枚举到x \ primes[j] 时,x就会被筛掉