poj3292

题目较简单。大致题意为给定4n+1数。例如1、5、9、13、……。将这些数分为unit(即为1),prime(也理解为”素数“,但并非真正的素数),composite。并规定一个semi-prime数为可为两个prime数乘积的4n+1数。题目给定一个4n+1数,要判断1——4n+1数之间(包含1和该4n+1数)的所有semi-prime个数。题目涉及到筛法求素数的运用。

首先说明一下这里筛法求素数的运用:

筛法求素数的思想是根据已知的素数筛掉该素数的倍数。素数的性质为:只能被1和其本身整除(不能被比该数小的整数整除)。而这里的prime数性质与素数有相似之处。如果只考虑4n+1数(题目中也是这样要求的),那么prime数的性质为:只能被1和其自身整除(不能被比它小的4n+1数整除)。性质一致。故可以直接采用筛法求素数思想求解prime数。

本题的思路如下:首先打表1000001范围内的所有prime数。然后打表1000001范围内所有1——4n+1数范围内的semi-prime数个数。对于主函数则根据事先打好的表,由于已经求出了所有结果,则直接输出即可。上面已经说明了求解prime数的方法(筛法)。下面接着说明一下判断semi-prime的方法及打表方法。

判断semi-prime方法:

首先semi-prime是4n+1数。然后其必须要为两个prime数的乘积。既然要为2个prime数乘积,那么我们可以枚举prime表,枚举所有小于该数的prime数,若存在能整除该数并且另一个因子也是prime数的prime数。则说明该数为semi-prime数。否则不是。枚举的时候要注意剪枝。剪枝的关键是:若一个数是semi-prime数,那么其一定不能为prime数(根据prime数的定义可以很简单的推出),其次该数的两个因子必定要小于该数(因子的关系无所谓),那么由乘法原理:可以推算枚举的最大限度为最小因子的平方要小于等于该数。这样可以很大程度优化判断。

打表方法:

在利用上述方法判段1000001范围内所有semi-prime数时。在上述方法的过程中增加一个Sum【4n+1】数组,用于存储该4n+1数范围内所有semi-prime数个数。具体为若4n+1数为semi-prime数,那么Sum【4n+1】=Sum【4n+1-4】+1;否则Sum【4n+1】=Sum【4n+1-4】;具体看程序

另外说明一下实现打表的有点。开始时只是事先打了第一张表即prime表。然后对于主程序中每次输入的h,都要判断1——n范围内的所有semi-prime数。结果TLE了。后来发现其实这完全没有必要。因为反复计算了许多次,其实只需要实现计算一次,在计算过程中保存Sum即可,主程序则可直接得出答案。这样的优化效率大大提升

下面是代码: 5376K+547MS

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define Max 89080 // 1000001范围内所有prime数个数上限,可事先程序计算
#define Maxx 1000010 // 4n+1数最大为1000001
int Sum[Maxx]; // 求和数组
int prime[Max]; // 保存prime数
bool flag[Maxx]; // 判断是否为prime数
int num,h; // num为prime数个数
void prime_table(int n){// 筛法求prime数,代码几乎后筛法求素数一样。因为思想一致
	int i,j,pivot=0;
	memset(flag,0,sizeof(flag)); // 全部初始化为不是prime数
	prime[pivot++]=5;
	flag[5]=true; //筛法核心代码
	for(i=9;i<=n;i+=4){
		bool trag=true;
		for(j=0;prime[j]*prime[j]<=i;j++)
			if(i%prime[j]==0)
				trag=false;
		if(trag){ //若为prime数,则flag设置为true
			prime[pivot++]=i;
			flag[i]=true;
		}
	}
	num=pivot; // prime数个数
}
void Get_num(int h){ // 判断是否为semi-prime数,再次过程中打第二张表
	int i,j;
	Sum[1]=0;//1——1范围内semi-prime个数为0
	for(i=5;i<=h;i+=4){ // 第二个4n+1数为5,第三个为9,  13 ,……
		if(flag[i]){ // 剪枝,若为prime数,则必定不是semi-prime数,Sum[i]=Sum[i]=Sum[i-4];
			Sum[i]=Sum[i-4];
			continue;
		}
		j=0;
		bool trag=false; // 设置标记 ,注意j<num条件。若没有该条件,则可能因为j>=num而产生不可预知的结果(死循环)
		while(j<num && prime[j]*prime[j]<=i){ // 剪枝关键:两数因子中较小的平方要小于等于乘积数。
			if(i%prime[j]==0 && flag[i/prime[j]]){
				trag=true;
				break;
			}
			j++;
		}
		if(trag) // 若为semi-prime数,则Sum[i]=Sum[i-4]+1;
			Sum[i]=Sum[i-4]+1;
	    else
			Sum[i]=Sum[i-4];
	}
}
int main(){
	prime_table(1000001); // 打第一张表
	Get_num(1000001); // 打第二张表
	while(scanf("%d",&h),h) 
		printf("%d %d\n",h,Sum[h]); // 直接输出
	return 0;
}


 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值