素数的定义
只被1和自己两个正整数整除的正整数被称作素数,也被称作质数。
与之相反的数便是合数
合数的定义是:不仅被1和自己两个正整数整除,还被其他的正整数整除的正整数为合数。
Tips: 1不是素数,也不是合数
素数的判定
朴素算法
根据定义,我们可以这样做:枚举2
到n-1
,如果枚举到的数整除了n
,那n
就不是质数,反之便是质数。
bool isPrime(int x)
{
if(x < 2) // 负数或者0、1都不是素数
return false;
for(int i = 2; i < n; i++)
if(x % i == 0)
return false;
return true;
}
这个算法的复杂度是\(O(n)\)的,显然没有什么大用,我们想办法来优化一下。
加一点点优化
一个数的因数有这样一个性质:比如\(n = x \times y\),则其中有一个一定小于等于\(\sqrt{n}\),另一个一定大于等于\(\sqrt{n}\),这十分显然就不证明了。
所以我们只需要判断\(1\)到 \(\sqrt{n}\)中有没有数可以整除\(n\)就行了。
bool isPrime(int x)
{
if(x < 2)
return false;
for(int i = 2; i * i < n; i++)
if(x % i == 0)
return false;
return true;
}
那这个算法的时间复杂度是\(O(\sqrt n)\),这就实用得多啦。
一种十分神奇的算法!
Miller Robbin算法……
……
PTRS 赛普瑞斯正在链接中……
素数筛算法
在我们需要求出一段素数时,我们显然不能一个一个枚举,然后再判断,这样肯定超时,但是素数筛算法很好的解决了这个问题。
埃氏筛(埃拉托斯特尼筛法)
埃氏筛是一个希腊数学家提出的筛质数的方法,非常滴好用和好写。
先说思路:
从\(2\)开始往后判断,如果这个数还不是其他质数的倍数,那么这个数就是一个质数。
看图: 首先,1
不是质数,先划掉。
然后\(2\),他还不是其他质数的倍数,说明他是一个质数,就把他存到素数数组里面,然后把他的所有倍数都标记上。
在看下一个数3,他不是任何质数的倍数,所以他是一个质数,把3的倍数标记上。
下一个数是4,他已经被标记过了,所以4是一个合数,跳过。
……
以此类推,我们就可以吧1~18里所有的质数筛出来。
代码非常滴好写:
#define MAXN 100005
bool flag[MAXN];
int prime[MAXN];
int cnt = 0;
void getprime()
{
for(int i = 2; i <= 100000; i++)
{
if(!flag[i]) //没有被标记过,是质数
{
cnt++;
prime[cnt] = i;
for(int j = i; j <= 100000; j += i) //把这个质数的所有倍数标记上。
{
flag[j] = 1;
}
}
}
}
这个算法的时间复杂度是\(O(n \times log\text{ }log\text{ }n)\)
非常滴快
欧拉筛(线性筛)
嘛,这是我知道最快的筛法(孤陋寡闻了),仔细看看上面的埃氏筛,我们会发现有的地方会被重复筛几次,比如\(6\),它会被\(2\)筛一次,也会被\(3\)筛一次,每次筛它花的时间很少,但是我们要找很大一串质数时,他就会积少成多,浪费许多时间,于是就出现了欧拉筛这种算法。
先看看代码:
#define MAXN 100005
int prime[MAXN];
bool flag[MAXN];
int cnt = 0;
void getprime()
{
for(int i = 2; i <= 100001; i++)
{
if(!flag[i])
{
cnt++;
prime[cnt] = i;
}
for(int j = 1; prime[j] * i <= 100001; j++)
{
flag[prime[j] * i] = 1;
if(i % prime[j] == 0)
break;
}
}
}
仔细看看,我们发现大概只多了这两行:
if(i % prime[j] == 0)
break;
那这两行神奇的代码有什么用呢?为什么要加这两行就可以优化了呢?
当\(i\)是\(prime_j\)的整数倍时,\(i \times prime_{j+1}\) 是 \(prime_j\) 的整数倍(因为\(i = x \times prime_j\)),不需要现在筛出,因为在之后这些数一定会被再筛一遍,所以break跳出循环就行了。
时间复杂度查不多是\(O(n)\)了。
线性筛和其他思路的搭配
线性筛的好处就是在于筛选和筛选后可以统计其他东西 -------wch
仔细想想线性筛还能干些什么:
比如这个:
话说育母蜘蛛搞了一批小蜘蛛……于是育母蜘蛛要跟小蜘蛛们玩个游戏……育母蜘蛛让小蜘蛛们按照 1 到K 的顺序报数,报完K 后再报 1,每次报K 的小蜘蛛就会受到惩罚,小蜘蛛们在报数之前就 知道谁要受惩罚,于是一些小蜘蛛结成了同盟,小蜘蛛的同 盟很有意思,两只小蜘蛛是同盟当且仅当两只小蜘蛛的最大公约数不为 1。
现在育母蜘蛛要带一些小蜘蛛去参加展览,育母蜘蛛希望带的 小蜘蛛里任意两只都不是同盟,现在育母蜘蛛想知道它最多能带多少 只小蜘蛛,在个数最多的情况下,育母蜘蛛还想知道所带的小蜘蛛总 的编号和最小是多少。
这题我们就可以用一个a
数组来存小蜘蛛的数量,sum
数组来小蜘蛛的最小编号和。
int prime[MAXN],sum[MAXN],a[MAXN];
bool vis[MAXN];
int Prime(int n)
{
int cnt = 0;
memset(vis, 0, sizeof(vis));
for(int i = 2; i < n; i++)
{
a[i] = a[i- 1];
sum[i] = sum[i - 1];
if(!vis[i])
{
prime[cnt] = i;
sum[i] += sum[i - 1] + i;
a[i] = ++cnt;
}
for(int j = 0; j < cnt && i * prime[j] < n; j++)
{
vis[i * prime[j]] = 1;
if(i % prime[j] == 0)
break;
}
}
return cnt;
}
还有一些其他的题目可以这样做,希望大家看了我的总结之后有所收获并且能够拓展延生。
\(The End\)