题目描述
有一天路飞突发奇想,他有一个猜想,任意一个大于 2 的偶数好像总能写成 2 个质数的和。路飞查了资料,发现这个猜想很早就被一个叫哥德巴赫的人提出来了,称为哥德巴赫猜想。目前还没有证明这个猜想的正确性。路飞告诉你一个整数 n ,让你用这个数去验证。
注意 1 不是质数。
输入
输入一个偶数 n(2≤n≤8000000) 。
输出
输出一个整数表示有多少对 (x,y) 满足 x+y=n(x≤y) 且x,y 均为质数。
样例输入1
10
样例输出1
2
数据规模与限定
时间限制:1 s
内存限制:64 M
直观方案:
#include <stdio.h>
#include <math.h>
int is_prime(int n){
for(int i=2,I=sqrt(n);i<=I;i++){
if(n%i) continue;
return 0;
}
return 1;
}
int main(){
int n,count=0;
scanf("%d",&n);
for(int i=2;i<=n/2;i++){
if(is_prime(i)&&is_prime(n-i)) count++;
}
printf("%d",count);
return 0;
}
对每个数都需要循环判断是否为质数,效率很低,容易超时:
分析
给的测试数据比较大,得从其他角度思考。
方案一:
给定数组primes,每一位初始化为0代表为质数,从i=2开始,当遇到0时,将2 * 2,2 * 3,2 * 4位置标记为1,依次类推,最终得到得primes数组,质数会被标记为0,合数标记为1。比如primes[2]=0代表2为质数,primes[9]=1代表9为合数。如果primes[i]=primes[n-i]=0,代表找到一组质数对,他们的和等于n。
代码如下:
#include <stdio.h>
#include <math.h>
#define MAX 8000000
int primes[MAX]={0};
void mark_primes(int n){
for(int i=2;i<=sqrt(n);i++){
if(!primes[i]){
for(int j=i*i;j<=n;j+=i){
primes[j]=1;
}
}
}
}
int main(){
int n,count=0;
scanf("%d",&n);
mark_primes(n);
for(int i=2;i<=n/2;i++){
if(!primes[i]&&!primes[n-i]) count++;
}
printf("%d",count);
return 0;
}
结果:
方案二
- 可以先利用素数筛,找到n以内的质数,得到primes数组
- 再循环查找,比如输入n为100时,2为质数,那么98是否在primes中(此时可以用二分查找)。
备注: 循环查找时,并不需要遍历整个primes,因为两个数之和如果等于n,那么必有一个小于等于n/2,另一个大于等于n/2。所以可以将primes根据n/2分成较小数部分和较大数部分,循环小数组部分得到质数1,在大数部分寻找质数2(n-质数1),如果能找到质数2,则所求质数对 +1.
代码实现:
#include <stdio.h>
#define MAX 8000000
int primes[MAX]={0};
//优化的素数筛,最后得到primes,首位表示n以内的质数总个数,后面依次为对应的质数2,3,5,7……
void get_primes(int n){
for(int i=2;i<=n;i++){
if(primes[i]==0) primes[++primes[0]]=i;
for(int j=1;i*primes[j]<=n;j++){
primes[i*primes[j]]=1;
if(i%primes[j]==0) break;
}
}
}
//二分查找,用于查找质数2是否在primes的后半段中
int bin_search(int *nums,int target,int len){
int left=0,right=len-1,mid=0;
while(left<=right){
mid=left+((right-left)>>1);
if(nums[mid]==target) return mid;
if(nums[mid]>target) right=mid-1;
else left=mid+1;
}
return -1;
}
//寻找右边界,用于查找数组中小于等于n/2的索引位置
int bin_search_right(int *nums,int target,int len){
int left=0,right=len,mid=0;
while(left<right){
mid=left+((right-left)>>1);
if(nums[mid]>target) right=mid;
else left=mid+1;
}
return left-1;
}
int main(){
int n,count=0;
scanf("%d",&n);
get_primes(n);
int flag=bin_search_right(&primes[1],n/2,primes[0])+1; //flag 将primes分为两部分,前部分小于等于n/2,后部分大于等于n/2(因为可以取两个相等的质数,所以左右区间都应该取n/2);计算边界时左边界从0开始,但是实际primes中的素数从1开始,所以最后使用flag时需要+1.
for(int i=1;i<=flag;i++){
if(~bin_search(&primes[flag],n-primes[i],primes[0]-flag+1)) count++; //在右边能找到质数2,则count++,右边部分的长度等于总长度-左边+1(加1是因为重复取了flag边界值),即“primes[0] - flag+1
}
printf("%d",count);
return 0;
}
结果:
小结
可以看到方案2的代码量要大于方案1,切运用到了二分查找,素数筛的方法。但是内存和时间都略优于方案1.