Day 2:数论——[筛法、gcdgcdgcd、lcmlcmlcm 和裴蜀定理]
一、筛法
注:
1.我们用 ps_is_prime 数组来表示暴力筛的 boolboolbool 类型判断素数数组(000 表示是质数,111 表示不是质数)。
2.我们用 ai_is_prime 数组来表示埃氏筛的 boolboolbool 类型判断素数数组。
2.我们用 ou_is_prime 数组来表示欧拉筛的 boolboolbool 类型判断素数数组,prime 来表示欧拉筛的素数表,cnt 来表示 prime 数组的长度。
1.暴力筛(ViolenceViolenceViolence screeningscreeningscreening methodmethodmethod)
①①① 原理
CaseCaseCase 111 初始化:一个常识:111 肯定不是质数,那么 ps_is_prime[1] 便置为 111 。
CaseCaseCase 222 筛数过程:先用 iii 枚举 2−n2 - n2−n 中的每一个数,再用 jjj 从 222 开始枚举,将 iii 的所有不超过 nnn 的倍数在 ps_is_prime 中标记为 111。
CaseCaseCase 333 小优化:由于每个数的因数都成对出现,所以只需要枚举到 n\sqrt nn 即可。
②②② 具体例子举例
例如:筛 202020 以内的质数。
首先,将 111 表示不是质数,预先算出 n=4\sqrt n = 4n=4
1−n1 - n1−n | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
ps_is_prime | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
第一次循环,i=2i = 2i=2
1−n1 - n1−n | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
ps_is_prime | 1 | 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 |
第二次循环,i=3i = 3i=3
1−n1 - n1−n | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
ps_is_prime | 1 | 0 | 0 | 1 | 0 | 1 | 0 | 1 | 1 | 1 | 0 | 1 | 0 | 1 | 1 | 1 | 0 | 1 | 0 | 1 |
第三次循环,i=4i = 4i=4
1−n1 - n1−n | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
ps_is_prime | 1 | 0 | 0 | 1 | 0 | 1 | 0 | 1 | 1 | 1 | 0 | 1 | 0 | 1 | 1 | 1 | 0 | 1 | 0 | 1 |
③③③ 代码展示
#include <bits/stdc++.h>
using namespace std;
const int N = 2e7 + 5;
int n;
bool ps_is_prime[N];
void ps_get(int m) {
ps_is_prime[1] = 1;
int nn = sqrt(m);
for (int i = 2;i <= nn;i++) {
for (int j = 2;j * i <= m;j++) {
ps_is_prime[i * j] = 1;
}
}
}
int main() {
cin >> n;
ps_get(n);
return 0;
}
④④④ 时间复杂度及证明
个人的一些理解,不要在意其准确性。
其实课堂上,郭老师说,外层循环从 2−n2 - n2−n 改为 2−n2 - \sqrt n2−n 时间复杂度有很大区别,但实际上区别很小。别急,先看我证明。
若是 2−n2 - n2−n :
时间复杂度 =n2+n3+n4……nn= \dfrac{n}{2} + \dfrac{n}{3} + \dfrac{n}{4} …… \dfrac{n}{n}=2n+3n+4n……nn
∵\because∵ n2⩽n2,n3+n4⩽n2,n5+n6+n7+n8⩽n2……\dfrac{n}{2} \leqslant \dfrac{n}{2}, \dfrac{n}{3} + \dfrac{n}{4} \leqslant \dfrac{n}{2},\dfrac{n}{5} + \dfrac{n}{6} + \dfrac{n}{7} + \dfrac{n}{8} \leqslant \dfrac{n}{2} ……2n⩽2n,3n+4n⩽2n,5n+6n+7n+8n⩽2n……
那么,一共有 log2n\log_2 nlog2n 个这样的不等式相加
即:时间复杂度 ⩽log2n⋅n2\leqslant \log_2 n \cdot \dfrac{n}{2}⩽log2n⋅2n
∴\therefore∴ 时间复杂度 ⩽log2n⋅n\leqslant \log_2 n \cdot n⩽log2n⋅n
所以,我证明出来时间复杂度应该是 ⩽log2n⋅n\leqslant \log_2 n \cdot n⩽log2n⋅n 的。
而如果是 2−n2 - \sqrt n2−n 的话,它的时间复杂度是 ⩽log2n⋅n\leqslant \log_2 \sqrt n \cdot n⩽log2n⋅n的,实际上 log2n\log_2 \sqrt nlog2n 和 log2\log_2log2 nnn 的差别并不大,例如,当 n=2×107n = 2 \times 10^7n=2×107 时, log2\log_2log2 n\sqrt nn ≈12,\approx 12,≈12, log2\log_2log2 nnn ≈24\approx 24≈24,只是相差了 222 倍 。
⑤⑤⑤ 实际用时
外层循环枚举 2−n2 - \sqrt n2−n 时
TLE 1104ms 86分
外层循环枚举 2−n2 - n2−n 时
TLE 1191ms 86分
2.埃氏筛(sievesievesieve ofofof EratosthenesEratosthenesEratosthenes)
①①① 原理
其实,埃氏筛就是在暴力筛的基础上多加了一个判断:如果这个数不是质数便跳过。
这样做有什么好处呢?
因为如果这个数不是质数,那么它就有其他的质因子,那么为了加快速度,我们要尽可能的保证一个数只被筛 111 次,要想保证这个,就要保证这个数尽可能被自己的最小质因子筛到,所以便可以跳过。
②②② 具体例子举例
例如:筛 202020 以内的质数。
首先,将 111 表示不是质数,预先算出 n=4\sqrt n = 4n=4
1−n1 - n1−n | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
ps_is_prime | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
第一次循环,i=2i = 2i=2
1−n1 - n1−n | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
ps_is_prime | 1 | 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 |
第二次循环,i=3i = 3i=3
1−n1 - n1−n | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
ps_is_prime | 1 | 0 | 0 | 1 | 0 | 1 | 0 | 1 | 1 | 1 | 0 | 1 | 0 | 1 | 1 | 1 | 0 | 1 | 0 | 1 |
第三次循环:这便是一个优化了,由于 444 不为质数,所以,这层循环直接跳过。
③③③ 代码展示
#include <bits/stdc++.h>
using namespace std;
const int N = 2e7 + 5;
int n;
bool ai_is_prime[N];
void ai_get(int m) {
ai_is_prime[1] = 1;
int nn = m;
for (int i = 2; i <= nn; i++) {
if (ai_is_prime[i])
continue;
for (int j = 2; j * i <= m; j++) {
ai_is_prime[i * j] = 1;
}
}
}
int main() {
cin >> n;
ai_get(n);
return 0;
}
④④④ 时间复杂度及证明
此部分摘自网上(本人觉得自身证明时间复杂度的能力不足,并且数学公式实在是太难打了)
当然,实际时间复杂度是大于等于O(logO(\logO(log log\loglog n)n)n) 的。
⑤⑤⑤ 实际用时
外层循环枚举 2−n2 - \sqrt n2−n 时
AC 336ms 100分
外层循环枚举 2−n2 - n2−n 时
AC 505ms 100分