判断单个数是否为素数
时间复杂度 O ( n ) O(\sqrt{n}) O(n)
bool check(int n){
int m=sqrt(n+0.5);
for(int i=2;i<=m;i++){
if(n%i==0) return false;
return true;
}
埃氏筛法
算法复杂度 O ( n ∗ l o g ( l o g n ) ) O( n*log(log n) ) O(n∗log(logn))
原理:从 2 2 2开始,每第一次出现且之前没有它的因数出现就一定是素数,然后再把它的所有倍数设为 t r u e true true。需要注意的点是,如果我们只需要判断 m a x n maxn maxn范围内的数是不是素数,那么外层循环设置为 s q r t ( m a x n + 0.5 ) sqrt(maxn+0.5) sqrt(maxn+0.5)即可。但是如果我们需要用数组保存 m a x n maxn maxn内的素数,那么外层循环也必须是 m a x n maxn maxn
const int maxn=?;
bool isprime[maxn]; //maxn即需要求多少范围里的素数
int prime[N]; //根据maxn的大小调节,该数组储存了maxn范围内的所有素数
int num;
void Prime(){
memset(isprime,true,sizeof(isprime));
int m=sqrt(maxn+0.5); //实际上从maxn开始也不会太费时间
for(int i=2;i<=m;i++){ //一个数的因数按其根号的值对称分布,因此只要能枚举到其因数便能筛选出其是否为素数
if(isprime[i]){ //凡是在下面一层循环里被设置为false的一定不是素数
//prime[num++]=i; //保存素数的话外层循环也必须是maxn
for(int j=2*i;j<maxn;j=j+i) //如果从i*i开始,有可能爆int
isprime[j]=false; //这里实现了筛选,即素数的倍数一定是合数
}
}
}
可以将 b o o l bool bool数组替换为 b i t s e t bitset bitset降低空间复杂度
bitset<maxn> isprime;
void getPrime() {
isprime.set();
isprime[0] = isprime[1] = 0;
int m = sqrt(maxn + 0.5);
for (int i = 2; i <= m; i++)
if (isprime[i]) {
for (int j = i * i; j < maxn; j += i) isprime[j] = 0;
}
}
欧拉筛法
算法复杂度 O ( n ) O(n) O(n)
原理:
任意一个正整数 k k k,若 k ≥ 2 k \geq 2 k≥2,则k可以表示成若干个质数相乘的形式。埃氏筛法中,在枚举 k k k的每一个质因子时,都计算了一次 k k k,从而造成了冗余。因此在改进算法中,只利用 k k k的最小质因子去计算一次 k k k
与埃氏筛法不同的是,对于外层枚举 i i i,无论 i i i是质数,还是是合数,我们都会用 i i i的倍数去筛。但在枚举的时候,我们只枚举 i i i的质数倍
此外,在从小到大依次枚举质数 p p p来计算 i i i的倍数时,还需要检查 i i i是否能够整除 p p p。若 i i i能够整除 p p p,则停止枚举 。若 i i i能整除 p p p,因为我们是从小到大枚举素数的,那么又可以说 i i i已经被 p p p筛过了,所以 i i i乘其他质数的结果也一定是 p p p的倍数,后面会通过 p p p筛出而不需再筛,因此直接退出。
综上,欧拉筛法可以保证每个合数只会被枚举到一次,时间复杂度为 O ( n ) O(n) O(n)
vector<int> prime;
bitset<maxn> vis;
void init() {
vis.reset();
for (int i = 2; i < maxn; i++) {
if (!vis[i]) prime.push_back(i);
for (int j = 0; j < prime.size() && i * prime[j] < maxn; j++) {
vis[i * prime[j]] = 1;
if (i % prime[j] == 0) break;
}
}
}
二次筛法
区间 [ l , r ] [l,r] [l,r]均小于 1 e 9 1e9 1e9且 b − a < = 1 e 7 b-a<=1e7 b−a<=1e7
因为一个非素数是被它的最小素数因子筛掉, 2147483647 2147483647 2147483647内的数要么是素数,要么是能被 s q r t ( 2147483647 ) sqrt(2147483647) sqrt(2147483647)内的素数整除的合数,也就是说, [ l , r ] [l,r] [l,r]区间的所有非素数的素数因子都在 s q r t ( 2147483647 ) sqrt(2147483647) sqrt(2147483647)内,预先将 s q r t ( 2147483647 ) sqrt(2147483647) sqrt(2147483647)内的所有素数找出来,然后用这些素数去筛掉指定区间的所有非素数
求出某个区间的素数且这些素数可能很大,需要进行下标偏移
const ll up = 2147483647;
const int maxn = 1e6 + 10;
vector<int> prime, ans;
bool is_prime[maxn], vis[maxn];
void euler() {
int m = sqrt(up + 0.5);
prime.clear();
memset(is_prime, 1, sizeof(is_prime));
is_prime[0] = is_prime[1] = 0;
for (int i = 2; i <= m; i++) {
if (is_prime[i]) prime.push_back(i);
for (int j = 0; j < prime.size() && 1LL * i * prime[j] <= m; j++) {
is_prime[i * prime[j]] = 0;
if (i % prime[j] == 0) break;
}
}
}
void solve(ll l,ll r) {
memset(vis, 1, sizeof vis);
if (l == 1) l++;
for (int i = 0; i < prime.size() && 2LL * prime[i] <= r; i++) {
if (prime[i] < l) {
ll x = l / prime[i], L;
if (x * prime[i] < l) L = (x + 1) * prime[i];
else L = x * prime[i];
for (ll j = L; j <= r; j += prime[i]) vis[j - l] = 0;
} else {
for (ll j = 2LL * prime[i]; j <= r; j += prime[i]) vis[j - l] = 0;
}
}
ans.clear();
for (int i = 0; i <= r - l; i++)
if (vis[i]) ans.push_back(i + l);
}