质数
试除法判断质数
因为一个数的因数都是成对出现的,比如 121212 的因数是 2,62,62,6 和 3,43,43,4 等等,所以我们只要枚举较小的那个因数就可以了,设较小的因数为 ddd ,要求的数为 nnn 则 nd\frac{n}{d}dn 和 ddd 都能整除 nnn ,即 d∣nd|nd∣n 且 nd∣n\frac{n}{d}|ndn∣n
所以枚举时,只枚举 d≤ndd \le \frac{n}{d}d≤dn 的数
整理得 d≤nd \le \sqrt{n}d≤n
Code
bool is_prime(long long x) {
if (x < 2) return false;
for (long long i = 2; i <= x / i; i ++ )
if (x % i == 0)
return false;
return true;
}
分解质因数
试除法
从小到大枚举所有数(既枚举了质数,又枚举了合数为什么不会出问题?因为在枚举到这个合数之前,就意味着把 2∼i−12\sim i-12∼i−1 之间是质因子全部除干净了,则当前的 nnn 当中已经不含所有 2∼i−12\sim i-12∼i−1 之间的质因子了,iii 中也不包含任何 2∼i−12\sim i-12∼i−1 之间的质因子,则 iii 一定是质数)
注意到,nnn 中最多只包含一个大于 n\sqrt{n}n 质因子,所有可以先枚举完小于 n\sqrt{n}n 的质因子,单独处理那个大于 n\sqrt{n}n 的质因子,优化时间复杂度 O(n)→O(logn∼n)O(n)\to O(logn\sim\sqrt{n})O(n)→O(logn∼n) (上界)
Code
给定 111 个正整数 aia_iai ,将每个数分解质因数,并按照质因数从小到大的顺序输出每个质因数的底数和指数。
void div(int n) {
for(int i = 2; i <= n / i; i++) {
if(n % i == 0) { //i 一定是质数
int s = 0;//存指数
while(n % i == 0) {
n /= i;
s++;
}
printf("%d %d\n", i, s);
}
}
if(n > 1) printf("%d %d\n", n, 1);
puts("");
}
筛质数
https://www.acwing.com/problem/content/870/
朴素筛法
从前往后取质数,把所有数的倍数全部删掉,这样筛过之后,剩下的数就是质数了
证明
对于任何一个没有被筛掉的数 ppp 而言,2∼p−12\sim p-12∼p−1 中的任何一个数都不是 ppp 的约数,所以 ppp 就是一个质数
Code
int prime[N], cnt;
bool st[N];
//筛从2->n 之间的质数
void get_prime(int n) {
for(int i = 2; i <= n; i++) {
if(!st[i]) { //如果一个数没有被筛过,那么它是质数
prime[cnt++] = n;
}
for(int j = i + i; j <= n; j += i) {
st[j] = true;
}
}
}
时间复杂度
O(n2+n3+n4+...+nn)=O(n(12+13+14+...+1n))O(\frac{n}{2}+\frac{n}{3}+\frac{n}{4}+...+\frac{n}{n})=O(n(\frac{1}{2}+\frac{1}{3}+\frac{1}{4}+...+\frac{1}{n}))O(2n+3n+4n+...+nn)=O(n(21+31+41+...+n1))
注意到,12+13+14+...+1n\frac{1}{2}+\frac{1}{3}+\frac{1}{4}+...+\frac{1}{n}21+31+41+...+n1 为调和级数
12+13+14+...+1n=ln(n)+c(c=0.57...)\frac{1}{2}+\frac{1}{3}+\frac{1}{4}+...+\frac{1}{n}=ln(n)+c(c=0.57...)21+31+41+...+n1=ln(n)+c(c=0.57...)
所以,时间复杂度为
O(nln(n))≤O(nlog(n))O(nln(n))\le O(nlog(n))O(nln(n))≤O(nlog(n))
朴素筛法的优化,埃氏筛法
不用把所有数的倍数删掉,只要把质数的倍数删掉就可以了,当一个数不是质数时,就不需要筛掉它所有的倍数,所以只需要稍微更改一下上面的循环即可
void get_prime(int n) {
for(int i = 2; i <= n; i++) {
if(!st[i]) { //如果一个数没有被筛过,那么它是质数
prime[cnt++] = n;
for(int j = i + i; j <= n; j += i) st[j] = true;
}
}
}
时间复杂度
根据质数定理:1∼n1\sim n1∼n 中有 nln(n)\frac{n}{ln(n)}ln(n)n 个质数,时间复杂度为 O(nlog(logn))→O(n)O(nlog(logn))\to O(n)O(nlog(logn))→O(n) (趋近于)
线性筛法
基本思路
把每个合数用它的质因子筛掉,但核心是 nnn 只会被它的最小质因子筛掉,每次筛掉当前质数和 iii 的乘积,当 imod primes[j]=0i \mod{primes[j]}=0imodprimes[j]=0 时 breakbreakbreak
正确性证明
- 当 imod primes[j]=0i \mod{primes[j]}=0imodprimes[j]=0 时,primes[j]primes[j]primes[j] 一定是 iii 的最小质因子,primes[j]primes[j]primes[j] 一定是 prime[j]×iprime[j]\times iprime[j]×i 的最小质因子 →\to→ 只用最小质因子筛掉了某个数
- 当 imod primes[j]≠0i \mod{primes[j]}\ne0imodprimes[j]=0 时,由于是从小到大枚举的所有质数,并且没有枚举到 iii 的任何一个质因子,说明 primes[j]primes[j]primes[j] 一定小于 iii 的所有质因子,prime[j]prime[j]prime[j] 也一定是 primes[j]×iprimes[j]\times iprimes[j]×i 的最小质因子
- 任何一个合数都会被筛掉
对于一个合数 xxx 一定存在一个最小质因子,假设 primes[j]primes[j]primes[j] 是 xxx 的最小质因子,当 iii 枚举到 x÷primes[j]x\div primes[j]x÷primes[j] ,一定在枚举到 xxx 之前,所有 xxx 一定会被筛掉,并且,每个数都只会被筛一次,所以它的时间复杂度是 O(n)O(n)O(n) 的
Code
void get_prime(int n) {
for(int i = 2; i <= n; i++) {
if(!st[i]) prime[cnt++] = i;
for(int j = 0; prime[j] <= n / i; j++) { //primes[j]*i<=n 只筛去小于n的质数
st[prime[j]*i] = true;
if(i % prime[j] == 0) break; //primes[j] 一定是 i 的最小质因子
}
}
}