2020年2月14日 素数筛
素数筛其实是一种打表的方法,用更小的时间复杂度打出来素数表,达到节省时间的目的,缺点是空间复杂度较大。刚开始做题就发现了这类题目占用的内存比之前的大的多,甚至有MLE的风险。。
先来写一下素数筛的原理与模板:
原理来源:素数筛法
整数的唯一分解定理:
任何一个大于1的自然数 N,如果N不为质数,都可以唯一分解成有限个质数的乘积N=P1 ^ a1 · P2 ^ a2 · P3 ^ a3 · … · Pn ^ an ,这里P1<P2<P3<…<Pn均为质数,其诸指数 ai 是正整数。
(:当然质数的话直接就是质数本身)
一、埃氏筛法 空间复杂度:O(nloglogn)
埃氏筛法的思想:
枚举每个素数,然后把他们的倍数都打上标记,从而达到筛出的目的
质数的倍数一定不是质数
埃氏筛的模板:
const int N=1e7;//埃氏筛
int n,cnt,prime[N];
bool b[N];
void get_prime()
{
memset(b,1,sizeof(b));
b[1]=0;
for(int i=2;i<=N;i++){
if(b[i]){
prime[++cnt]=i;
for(int j=2;j*i<=N;j++)
b[i*j]=0;
}
}
}
埃氏筛的适用范围大约是1e6~1e7,1e7左右就有TLE的风险。
而且埃氏筛法还有一个缺陷 :
对于一个合数,有可能被筛多次。例如 30 = 2 * 15 = 3 * 10 = 5*6……
那么如何确保每个合数只被筛选一次呢?我们只要用它的最小质因子来筛选即可,这便是欧拉筛法。
二、欧拉筛法(线性筛法) 空间复杂度几乎为O(n)
基本思想:在埃氏筛法的基础上,让每个合数只被它的最小质因子筛选一次,以达到不重复的目的,每个数都只筛一次,相当于直接判断。
const int N=4e7;
int n,cnt,prime[N+10];
bool b[N+10];
void get_prime(int n)
{
memset(b,1,sizeof(b));
b[0]=b[1]=0;
for(int i=2;i<=n;i++){
if(b[i])
prime[++cnt]=i;
for(int j=1;j<=cnt && i*prime[j]<=n;j++){
b[i*prime[j]]=0;
if(i%prime[j]==0) break;
}
}
}
看一下今天的题目,经过jwGG的数据加强,感觉今天的题目很有挑战性,即使强如大佬,今天也没有AK。
林大OJ 585 最大素因子
如果要用memset函数把数组初始化为1,则不能开int数组,而要用bool数组。而且如果开int数组可能会MLE。
#include <bits/stdc++.h>
using namespace std;
const int N=1e6;
int n,cnt,prime[N],a[N],b[N];
void get_prime()
{
cnt=1;
memset(b,1,sizeof(b));
b[0]=b[1]=0;
for(int i=2;i<=N;i++){
if(b[i]) prime[cnt++]=i;
for(int j=1;j<=cnt && prime[j]*i<=N;j++){
b[prime[j]*i]=0;
if(i%prime[j]==0) break;//保证每个合数只会被它的最小质因数筛去,因此每个数只会被标记一次
}
}
for(int i=1;i<=N;i++){
if(b[i]) a[i]=a[i-1]+1;
else a[i]=a[i-1];
}
}
int main()
{
ios::sync_with_stdio(false);
get_prime();
while(cin>>n){
if(n==1){
cout<<"0"<<endl;
continue;
}
for(int i=n;i>=2;i--){//注意是i>=2,而不是i>=sqrt(n)或i>=n/2
if(n%i==0 && b[i]){
cout<<a[i]<<endl;
break;
}
}
}
return 0;
}
林大OJ 586 纯素数
这道题的难点在于如何把最高位去掉。解决方法是反着来:先取各位上的数,然后从1位数开始倒着加回来。
#include <bits/stdc++.h>
using namespace std;
const int N=1e6;
int n,cnt,prime[N+10];
bool b[N+10];
void get_prime()
{
memset(b,1,sizeof(b));
b[1]=0;
for(int i=2;i<=N;i++){
if(b[i]) prime[++cnt]=i;
for(int j=1;j<=cnt && i*prime[j]<=N;j++){
b[i*prime[j]]=0;
if(i%prime[j]==0) break;
}
}
}
bool judge(int x)
{
int a[8],len=0;
memset(a,0,sizeof(a));
while(x){
a[++len]=x%10;
x/=10;
}
for(int i=1;i<=len;i++){
int sum=0;
for(int j=i;j>=1;j--)
sum=sum*10+a[j];
if(!b[sum]) return 0;
}
return 1;
}
int main()
{
ios::sync_with_stdio(false);
get_prime();
while(cin>>n){
if(judge(n)) printf("YES\n");
else printf("NO\n");
}
return 0;
}
林大OJ 587 半素数
让该数与其的因子同是在素数表内,则算作成功。
#include <bits/stdc++.h>
using namespace std;
const int N=1e6;
int n,cnt,prime[N+10];
bool b[N+10];
void get_prime()
{
memset(b,1,sizeof(b));
b[1]=0;
for(int i=2;i<=N;i++){
if(b[i]) prime[++cnt]=i;
for(int j=1;j<=cnt && i*prime[j]<=N;j++){
b[i*prime[j]]=0;
if(i%prime[j]==0) break;
}
}
}
bool judge(int n)
{
for(int i=2;i*i<=n;i++)
if(n%i==0 && b[i] && b[n/i]) return 1;
return 0;
}
int main()
{
ios::sync_with_stdio(false);
get_prime();
while(cin>>n){
if(judge(n)) printf("YES\n");
else printf("NO\n");
}
return 0;
}
林大OJ 781 素数与数论
#include <bits/stdc++.h>
using namespace std;
const int N=1e6;
int n,cnt,prime[N+10],num[N+10];//num数组记录1~i有几个素数
bool b[N+10];
void get_prime()
{
memset(b,1,sizeof(b));
b[1]=0;
for(int i=2;i<=N;i++){
if(b[i]) prime[++cnt]=i;
for(int j=1;j<=cnt && i*prime[j]<=N;j++){
b[i*prime[j]]=0;
if(i%prime[j]==0) break;
}
num[i]=cnt;
}
}
int main()
{
ios::sync_with_stdio(false);
int a,b;
get_prime();
while(cin>>a>>b){
printf("%d\n",num[b]-num[a-1]);//左端点也可能是素数
}
return 0;
}
林大OJ 825 函数版素数判定
判定素数的模版题。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e7;
int prime[N+10];
bool b[N+10];
int cnt=1;
int init()
{
memset(b,1,sizeof(b));
b[0]=b[1]=0;
for(int i=2;i<=N;i++){
if(b[i])
prime[cnt++]=i;
for(int j=1;j<=cnt && prime[j]*i<=N;j++){
b[prime[j]*i]=0;
if(i%prime[j]==0) break;
}
}
return 0;
}
int main()
{
LL n;
init();
while(~scanf("%lld",&n)){
if(b[n]) printf("YES\n");
else printf("NO\n");
}
return 0;
}
林大OJ 2113 / 洛谷P3383 素数线性筛
线性筛(欧拉筛)的模版题。
#include <bits/stdc++.h>
using namespace std;
const int N=4e7;
int n,cnt,prime[N+10];
bool b[N+10];
void get_prime(int n)
{
memset(b,1,sizeof(b));
b[0]=b[1]=0;
for(int i=2;i<=n;i++){
if(b[i])
prime[++cnt]=i;
for(int j=1;j<=cnt && i*prime[j]<=n;j++){
b[i*prime[j]]=0;
if(i%prime[j]==0) break;
}
}
}
int main()
{
int n,q;
scanf("%d %d",&n,&q);
get_prime(n);
while(q--){
int k;
scanf("%d",&k);
printf("%d\n",prime[k]);
}
return 0;
}
林大OJ 1704 知否知否,应是绿肥红瘦
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e7+1;
int prime[N],b[N];
int cnt=1;
int init()
{
memset(b,1,sizeof(b));
b[0]=b[1]=0;
for(int i=2;i<N;i++){
if(b[i])
prime[cnt++]=i;
for(int j=1;j<=cnt && prime[j]*i<=N;j++){
b[prime[j]*i]=0;
if(i%prime[j]==0) break;
}
}
return 0;
}
int ss(LL n)
{
int flag=0;
for(int i=1;prime[i]<=sqrt(n*1.0);i++)
if(n%prime[i]==0){
flag=1;break;
}
if(n==1) flag=1;
return flag;
}
int main()
{
ios::sync_with_stdio(false);
int n;
LL x,y,z;
init();
cin>>n;
while(n--){
cin>>x>>y>>z;
if(ss(x+y-z)==0) cout<<"yes"<<endl;
else cout<<"no"<<endl;
}
return 0;
}
林大OJ 1262 五十弦翻塞外声
答案来自:大佬的题解。用试除法,不打素数表。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll t,x;
ll get_ans()
{
ll ans=1;
for(ll i=2;i*i<=x;i++)
{
if(x%i==0)
{
ll tmp=1;
while(x%i==0)//将x除以其因子(必是x的素因子)
{
tmp=tmp*i;
x=x/i;
}
ans*=(tmp*i-1)/(i-1);
}
}
if(x>1)ans*=(1+x);//注意,若x未被除尽到1,说明最后的x还剩下一个素数没有乘到答案里面,把它乘进去即可
return ans;
}
int main()
{
ios::sync_with_stdio(false);
cin>>t;
while(t--)
{
cin>>x;
printf("%lld\n",get_ans());
}
return 0;
}
林大OJ 1321 差点是素数
终极压轴大题,答案待补全。。
希望有一天我能有热情解出这道题来。