质数介绍
质数:只有两个正因数(1和自己)的自然数即为质数。比1大但不是素数的数称为合数。1和0既非素数也非合数。
质数的一些性质:
- N以内的素数的个数随着N的增大趋近于log(n)
- 从不大于n的自然数随机选一个数,它是素数的概率大约是1/In n(素数定理)
- 随着n的增大素数越来越稀疏。
- 在一个大于1的数 a和它的2倍之间(即区间(a,2a]中)必存在至少1个素数。
1.埃氏筛法
埃氏筛法:
const int maxn=10001;
int prime[maxn],num=0;
bool p[maxn];
void Find_Prime(){
memset(p,0,sizeof p);//默认全素数
for(int i=2;i<maxn;i++)
if(p[i]==false){
prime[num++]=i;
for(int j=i+i;j<maxn;j+=i) p[j]=true;
}
}
例题:素数
题目介绍:
输入一个整数n(2<=n<=10000),要求输出所有从1到这个整数之间(不包括1和这个整数)个位为1的素数,如果没有则输出-1。
思路:
筛法枚举所有素数,然后vector存储每组数据答案,便于输出
代码:
#include<bits/stdc++.h>
#define LL long long
#define fo(i,a,b) for(int i=a;i<b;i++)
#define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
using namespace std;
const int maxn=10001;
int prime[maxn],num=0;
bool p[maxn];
vector<int> v;
void Find_Prime(){
memset(p,0,sizeof p);
for(int i=2;i<maxn;i++)
if(p[i]==false){
prime[num++]=i;
for(int j=i+i;j<maxn;j+=i) p[j]=true;
}
}//埃氏筛
int main(){
int n;
Find_Prime();
while(~scanf("%d",&n)){
v.erase(v.begin(),v.end());
int count=0;
for(int i=0;i<maxn;i++){
if(prime[i]<n&&prime[i]%10==1){
count++;
v.push_back(prime[i]);
}
else if(prime[i]>=n) break;
}
int len=v.size();
for(int i=0;i<len;i++){
if(i!=len-1)printf("%d ",v[i]);
else printf("%d",v[i]);
}
if(count==0) printf("-1");
printf("\n");
}
return 0;
}
2.欧拉筛法
首先,我们知道当一个数为素数的时候,它的倍数肯定不是素数。所以我们可以从2开始通过乘积筛掉所有的合数。 将所有合数标记,保证不被重复筛除,时间复杂度为 O ( n ) O(n) O(n)
#include <bits/stdc++.h>
#define ll long long
const int maxn=1e6+10;
using namespace std;
bool vis[maxn];
ll prime[maxn];
int tot=0;
void init(){//O(n)欧拉筛
vis[1]=1;//突出1不是素数,可不写
for(int i=2;i<maxn;i++){
if(!vis[i]) prime[++tot]=i;
for(int j=1;j<=tot&&i*prime[j]<maxn;j++){
vis[i*prime[j]]=1;
if(i%prime[j]==0) break;
}
}
}
int main(){
init();//找出maxn-1以内的素数
for(int i=1;i<10;i++)printf("%d ",prime[i]);//输出前9位素数
return 0;
}
解释:
- v i s [ i ∗ p r i m e [ j ] ] = 1 ; vis[i*prime[j]]=1; vis[i∗prime[j]]=1; 这里不是像埃氏筛一样用 i i i 的倍数来消去合数,而是把 prime里面记录的素数,升序来当做要消去合数的最小素因子,这里 i i i 在消去合数中的作用是当做倍数的
- 当 i i i 是 p r i m e [ j ] prime[j] prime[j]的整数倍 (i%prime[j]==0) 时, i ∗ p r i m e [ j + 1 ] i*prime[j+1] i∗prime[j+1]肯定被筛过,跳出循环。
例题:
思路(借鉴dsy):
代码:
#include<bits/stdc++.h>
using namespace std;
int n;
const long long mod = 1e9+7;
bool b[80000100];
int p[10000000];
int cnt = 0;
long long ksm(int a, int b){
long long ans= 1, t= a;
while(b){
if (b & 1) ans = ans * t % mod;
t = t * t %mod;
b >>= 1;
}
return ans;
}
long long calc(int x){
if (x == 2)
return ksm(2, floor(log(n/3)/ log(2)));
return ksm(x, floor(log(n/2)/ log(x)));
}
int main(){
cin>> n;
long long ans = 1;
memset(b, 1, sizeof(b));//初始全素数
for(int i = 2;i <= n/2; i++){//筛到n/2即可
if(b[i]){
p[cnt++] = i;
ans = (ans * calc(i)) % mod;//计算贡献
}
for(int j = 0;j < cnt && i*p[j] <= n/2; j++){
b[i*p[j]] = 0;//筛去合数
if(i % p[j] == 0) break;
}
}
if (ans == 1) cout <<"empty";
else cout <<ans;
return 0;
}
两种筛法区别
例题
1059 Prime Factors (25分)
法一(无筛法)
#include<bits/stdc++.h>
#define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
using namespace std;
const int maxn = 100010;
bool is_prime(int n){
if(n == 1) return false;
int sqr = (int)sqrt(1.0*n);
for(int i = 2; i <= sqr; i++){
if(n % i == 0) return false;
}
return true;
}
int prime[maxn],pNum = 0;
void Find_prime(){
for(int i = 1 ; i < maxn; i++){
if(is_prime(i) == true){
prime[pNum++] = i;
}
}
}
struct facot{
int x,cnt;
}fac[10];//x为质因子,cnt为其个数
int main(){
Find_prime();
int n;
scanf("%d",&n);
int num = 0;
if(n == 1) printf("1=1");
else{
printf("%d=",n);
int sqr = (int)sqrt(1.0*n);
for(int i = 0; i < pNum ; i++){
if(n % prime[i] == 0){
fac[num].x = prime[i];
fac[num].cnt = 0;
while(n % prime[i] == 0){
fac[num].cnt++;
n /= prime[i];
}
num++;
}
if(n == 1) break;
}
if(n != 1){//寻找是否有唯一大于根号n的质因子
fac[num].x = n;
fac[num].cnt = 1;
}
for(int i = 0; i < num; i++){
if(i > 0) printf("*");
printf("%d",fac[i].x);
if(fac[i].cnt > 1) printf("^%d",fac[i].cnt);
}
}
return 0;
}
法二(埃氏筛)
#include<bits/stdc++.h>
#define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
using namespace std;
const int maxn = 100010;
int prime[maxn],pNum = 0;
void Find_prime(){
int p[maxn];
memset(p,0,sizeof(p));//默认全是素数
for(int i=2;i<maxn;i++){
if(p[i]==0){
prime[pNum++]=i;
for(int j=2*i;j<maxn;j+=i)p[j]=1;
}
}
}
struct facot{
int x,cnt;
}fac[10];//x为质因子,cnt为其个数
int main(){
Find_prime();
int n;
scanf("%d",&n);
int num = 0;
if(n == 1) printf("1=1");
else{
printf("%d=",n);
int sqr = (int)sqrt(1.0*n);
for(int i = 0; i < pNum ; i++){
if(n % prime[i] == 0){
fac[num].x = prime[i];
fac[num].cnt = 0;
while(n % prime[i] == 0){
fac[num].cnt++;
n /= prime[i];
}
num++;
}
if(n == 1) break;
}
if(n != 1){//寻找是否有唯一大于根号n的质因子
fac[num].x = n;
fac[num].cnt = 1;
}
for(int i = 0; i < num; i++){
if(i > 0) printf("*");
printf("%d",fac[i].x);
if(fac[i].cnt > 1) printf("^%d",fac[i].cnt);
}
}
return 0;
}
小W 的质数(prime)
Description
小X是一位热爱数学的男孩子,在茫茫的数字中,他对质数更有一种独特的情感。小X认为,质数是一切自然数起源的地方。
在小X的认知里,质数是除了本身和1以外,没有其他因数的数字。但由于小X对质数的热爱超乎寻常,所以小X同样喜欢那些虽然不是质数,但却是由两个质数相乘得来的数。 于是,我们定义,一个数是小X喜欢的数,当且仅当其是一个质数,或是两个质数的乘积。 而现在,小X想要知道,在L到R之间,有多少数是他喜欢的数呢?
Input
第一行输入一个正整数Q,表示询问的组数。
接下来Q行,包含两个正整数L和R,保证L≤R。
Output
输出Q行,每行一个整数,表示小X喜欢的数的个数。
Input
10
282 491
31 178
645 856
227 367
267 487
474 697
219 468
582 792
315 612
249 307
Output
97
78
92
65
102
98
114
90
133
29
思路:
这道题需要欧拉筛(线性筛),因为线性筛就是筛出素数并且剔除掉素数与另一个数 i i i 的乘积,对于两个素数的乘积,我们只需要判断一下 i i i 是不是素数就好了,不过因为询问较多,最后要用前缀和处理一下.
代码:
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <queue>
#include <cstring>
#include <set>
#include <map>
#include <sstream>
#define LL long long
#define mem(f, x) memset(f,x,sizeof(f))
#define fo(i,a,n) for(int i=a;i<n;++i)
#define fo2(i,a,n) for(int i=a;i<=n;++i)
using namespace std;
template<class T>inline void read(T &x){
x=0; char c=getchar(); bool f=0;
while(!isdigit(c))f^=c=='-',c=getchar();
while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
if(f)x=-x;
}
LL f[10000005], prime[10000005], tot = 0;
//vis:素数,vis2:素数乘积
LL vis[10000005], vis2[10000005];
long long sum[10000005];
struct node{
int l, r;
}a[100005];
void init(int r){//欧拉筛
fo2(i,2,r){
if (!f[i]){//发现素数了
prime[++tot] = i;
vis[i] = 1;
}
fo2(j,1,tot){
int t = i * prime[j];
if (t > r)break;
f[t] = 1;
if (vis[i])vis2[t] = 1;//统计素数和素数乘积
if (i % prime[j] == 0)break;
}
}
}
int main(){
int q;
LL maxn=0;
read(q);
fo2(i,1,q){
read(a[i].l);read(a[i].r);
maxn = max(maxn,1LL*a[i].r);
}
init(maxn);
//前缀和实现记忆化
fo2(i,2,maxn)sum[i] = sum[i - 1] + max(vis[i],vis2[i]);
fo2(i,1,q)printf("%lld\n", sum[a[i].r] - sum[a[i].l - 1]);
return 0;
}
斐波那契数列(质因子分解)
代码:
//分解质因数的好方法
#include<bits/stdc++.h>
using namespace std;
long long n,f[49],x=1;
const long long p=pow(2,31);
int main(){
cin>>n;
f[1]=1;
f[2]=1;
for(int i=3;i<=n;i++)
f[i]=(f[i-1]+f[i-2])%p;
f[n]%=p;
cout<<f[n]<<"=";
for(int i=2;i<=f[n];i++){
while(!(f[n]%i)){//寻找因子
if(x){x=0;cout<<i;}
else cout<<'*'<<i;
f[n]/=i;
}//这种方法保证寻找的都是质因子
}
cout<<endl;
}