今天来填一填坑了,虽然讲该内容显得不那么紧要。但是为了知识的完整性,我希望能够去进行补充吧。
其实任何人也还是需要经过这个过程的。hhhhhh…
参考书籍:
《算法笔记》
《算法竞赛入门经典-刘汝佳》
参考资料以及链接:
素数的讲解(希望能看看这篇博客真的是太棒了)
相应题目:
素数,质因子分解 | acwing,pat,leetcode |
---|---|
acwing | AcWing 866. 试除法判定质数 |
acwing | 867. 分解质因数 |
acwing | 868. 筛质数 |
acwing | AcWing 869. 试除法求约数 |
leetcode | 204. 计数质数 |
pat-A-1059 | 1059 Prime Factors (25分) |
pat-B-1007 | 1007 素数对猜想 (20分) |
从数论开始谈及,希望不要因为数论的两个字而唬住同学,它仅仅是一个代名词而已。
数论就从素数开始吧,我第一次认识素数,并且写代码应该还是大一的时候,那么时候仅仅是对该知识点的记忆吧。第一次的深刻理解(现在看来当时学得可真差)可能还是翁凯老师的c语言程序设计吧。
素数从百度百科给出如下的定义:质数是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。
根据这个定义,很容易就可以写出代码来判断某一个数是否为素数了。
代码的模板如下(哈哈哈,似乎这个是最通用的吧):
bool is_prime(int n){
if(n<=1) return false;
for(int i=2;i<n/i;i++){
if(n%i==0) return false
}
return true;
}
这是稍微已经进行了优化的写法,算法的复杂度在O(sqrt(n)),看起来不错呀,好像也还可以呀。但是如果为了过leetcode204,似乎是不可以的。如果是为了过pat的乙级7和甲级59,普通的素数判定基本就可以搞定了。我认为leetcode204是考察**埃氏筛法
,或者再往前一步,考察的是线性筛法
**。
既然学无止境,就要往前去跨一步。计算机是笨拙的,我们人类呢,希望操作的过程能够不重复,如果可以的话,更希望能用到现成的东西。
说到埃氏筛法,不要恐惧,仅仅是一个优化的过程。它的思想和暴力判断素数的思想恰恰相反。暴力判断素数是逐个判断,并选取出素数。但是埃氏呢,他是剔除掉非素数,那么剩下的就是素数啦。注意:优化就是在剔除的过程中去进行剔除的。
埃氏筛法思想:剔除掉素数的倍数
比如现在我要去获得100以内的素数
2的倍数是4,6,8,10,12…
3的倍数是6,9,12,15
…
这样的效率高了很多
时间复杂度为O(nlnlnn)
似乎优化了很多……
代码的模版如下
#include
using namespace std;
const int N=1000010;
int primes[N];//质数表
bool st[N];//判断是否为质数
int cnt;//计算质数的个数
//埃氏筛法-->线性筛法
//在10e7 线性筛法比埃氏筛法更快,快一倍
void linear_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的最小质因子
//1.i%primes[j]==0:primes[j]一定是i的最小质因子,primes[j]也一定是primes[j]*i的最小质因子
//2.i%primes[j]!=0:primes[j]也一定是primes[j]*i的最小质因子
}
}
}
}
int main(){
int n;
cin>>n;
linear_get_primes(n);
cout<<cnt;
return 0;
}
但是不知道同学有没有发现埃氏筛法有一个小缺陷,相信大家都发现了。在剔除倍数的时候,好像有重复了。怎么个重复法呢?
那就举举例子吧……,在上述的埃氏晒法中
2的倍数为2,4,6,8…30…60
3的倍数为6,9,12,15…30…60
5的倍数为10,15,20…60
似乎我们在剔除60的时候重复遇到了三次,数字越大,其实出现的次数是更多的。假设这个数n的范围之内,假设n之内素数存在n/ln n个质数(由质数定理可得),那个n就会连续被剔除的n/ln n次。其中复杂度为**O(nlnlnn)
**
那这样又存在了优化呀。
这次的优化叫做线性筛法,又叫欧拉筛法。
虽然说了思想,但是操作起来好像还是不那么容易呀。
虽然2,3,5都重复遇到了30。难道我在确定一个素数的过程中还要一直往后找?是不用的,走好当下就够了,优化好当下就可以了。
详情请看看上面的博客,一定要看,上面的博客可能是我见过最通俗易懂的博客了。
#include
using namespace std;
const int N=1000010;
int primes[N];//质数表
bool st[N];//判断是否为质数
int cnt;//计算质数的个数
//埃氏筛法-->线性筛法
//在10e7 线性筛法比埃氏筛法更快,快一倍
void linear_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的最小质因子
//1.i%primes[j]==0:primes[j]一定是i的最小质因子,primes[j]也一定是primes[j]*i的最小质因子
//2.i%primes[j]!=0:primes[j]也一定是primes[j]*i的最小质因子
}
}
}
}
int main(){
int n;
cin>>n;
get_primes(n);
cout<<cnt;
return 0;
}
素数的基本内容就讲解完了
回顾一下内容的基本讲解如下:暴力->埃氏筛法->线性筛除。请读者尽量在脑子里过一下内容。
接下来是例题讲解:
class Solution {
public:
int countPrimes(int n) {
//要使用埃氏筛法,或者线性筛法,不然数据集过不去
vector<int> primes;
vector<bool> isnp(n+1);//构造一个素数表,is no prime
for(int i=2;i<n;i++){
if(!isnp[i]) primes.push_back(i);
for(auto p:primes){
if(p*i>n) break;
isnp[p*i]=true;
if(i%p==0) break;
}
}
return primes.size();
}
};
ps:pat这两题都是比较简单的,基本上是在格式上进行了考察。在数据上,不会像leetcode那样卡得那么死。
pat-A-1059
pat-A-1059 Prime Factors (25分)
pat-B-1007
pat-A-1059 素数对猜想 (20分)