1619 例题 Prime Distance(POJ2689 LOJ10197 UVA10140 提高+/省选-) 线性筛 映射关系 0分 筛出区间内质数 超时0分 求任意区间质数的筛子100分

总目录

在线测评地址(ybt)   只有1个测试点

在线测评地址(POJ)   无法提交

在线测评地址(LOJ)   只有1个测试点

在线测评地址(LUOGU)   要有UVA账号才能提交

1.0分代码,使用了位置与数值,数值与位置的映射关系,确定了数的左最近质数,右最近质数。 

ybt

未通过

测试点结果内存时间
测试点1运行错误159432KB130MS

LOJ

看完题,马上跳出 线性筛,但看看数据范围1<=L< U<=2,147,483,647,即2.1*10^9,明显超时,似乎要从RL≤10^6突破,目前没什么感觉的情况下,先编个线性筛,在1-100区间,用25个质数,找找规律,再拓展到10^7找找规律,先找规律,再图突破2.1*10^9,当然肯定有技巧的,看看能否触碰到技巧,若不行,再学习 提高+/省选- 的技巧,也是蛮好。

线性筛代码如下:

#include <bits/stdc++.h>
#define maxn 10000010
using namespace std;
int prime[maxn],tot=0,b[maxn];
void linear(int x){
	int i,j;
	for(i=2;i<=x;i++)b[i]=0;
	for(i=2;i<=x;i++){
		if(b[i]==0)prime[++tot]=i;
		for(j=1;j<=tot&&prime[j]*i<=x;j++){
			b[prime[j]*i]=1;
			if(i%prime[j]==0)break;
		}
	}
}
int main(){
	int n,i;
	scanf("%d",&n);
	linear(n);
	for(i=1;i<=tot;i++)printf("%d ",prime[i]);
	return 0;
}

对应100以内的输入输出数据如下:

100
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

100以内25个质数间距

(2 3)间距1 
(3 5)间距2 
(5 7)间距2 
(7 11)间距4 
(11 13)间距2 
(13 17)间距4 
(17 19)间距2
(19 23)间距2 
(23 29)间距2 
(29 31)间距2 
(31 37)间距6 
(37 41)间距4 
(41 43)间距2 
(43 47)间距4
(47 53)间距7 
(53 59)间距6 
(59 61)间距2 
(61 67)间距6 
(67 71)间距4 
(71 73)间距2 
(73 79)间距6 
(79 83)间距4 
(83 89)间距6 
(89 97)间距8

找规律的同时,打表应该也是好办法,猜测该题,肯定是将2^31范围内的质数打出,进行研究,进而出了该题,也一直好奇2^31范围内的质数有多少呢,2^31=2.1*10^9,猜测应该在10^7个以内,打出来看看。

尝试了打表,发现数组开不了2,147,483,647这么大。

重心放在找规律上。

#include <bits/stdc++.h>
#define maxn 10000010
using namespace std;
int prime[maxn],tot=0,b[maxn],pos[maxn],left[maxn],right[maxn];
void linear(int x){
	int i,j;
	for(i=2;i<=x;i++)b[i]=0;
	for(i=2;i<=x;i++){
		if(b[i]==0)prime[++tot]=i,pos[i]=tot;
		for(j=1;j<=tot&&prime[j]*i<=x;j++){
			b[prime[j]*i]=1;
			if(i%prime[j]==0)break;
		}
	}
}
int main(){
	int n,i,a,b;
	linear(10000000);
	while(scanf("%d%d",&a,&b)!=EOF){
		
	}
	return 0;
}

目前情况,将10^7以内的情况编出,感觉还是不容易,先编出,再说。

统计了10^7以内的质数数量,有664579个,统计代码如下:

#include <bits/stdc++.h>
#define maxn 10000010
using namespace std;
int prime[maxn],tot=0,b[maxn],pos[maxn],left[maxn],right[maxn];
void linear(int x){
	int i,j;
	for(i=2;i<=x;i++)b[i]=0;
	for(i=2;i<=x;i++){
		if(b[i]==0)prime[++tot]=i,pos[i]=tot;
		for(j=1;j<=tot&&prime[j]*i<=x;j++){
			b[prime[j]*i]=1;
			if(i%prime[j]==0)break;
		}
	}
}
int main(){
	int n,i,a,b;
	linear(10000000);
	printf("tot=%d\n",tot); 
	return 0;
}

对应的输入输出数据如下:

tot=664579

按枚举的方式,10^7以内的最近,最远,距离应该可以编出。

打算标记10^7以内的数的左边最靠近的质数,右边最靠近的质数,如下

left[2]=2,right[2]=2
left[3]=3,right[3]=3
left[4]=3,right[4]=5
left[5]=5,right[5]=5
left[6]=5,right[6]=7

这样统计,比较方便的能确定靠近某个数的质数。

样例通过的0分代码如下:

#include <bits/stdc++.h>
#define maxn 10000010
using namespace std;
int prime[maxn],tot=0,b[maxn],pos[maxn],lt[maxn],rt[maxn];
void linear(int x){
	int i,j;
	for(i=2;i<=x;i++)b[i]=0;
	for(i=2;i<=x;i++){
		if(b[i]==0)prime[++tot]=i,pos[i]=tot;//pos[i]=tot确定质数i与位次tot的映射关系 
		for(j=1;j<=tot&&prime[j]*i<=x;j++){
			b[prime[j]*i]=1;
			if(i%prime[j]==0)break;
		}
	}
}
int main(){
	int n,i,j,a,b,mn,mx,c,d1,d2,d3,d4;
	linear(10000000);
	rt[1]=2;
	for(i=2;i<=tot;i++){
		lt[prime[i-1]]=rt[prime[i-1]]=prime[i-1];//质数的左近,右近质数是自己本身 
		lt[prime[i]]=rt[prime[i]]=prime[i]; 
		for(j=prime[i-1]+1;j<=prime[i]-1;j++)
			lt[j]=prime[i-1],rt[j]=prime[i];//数j左最近质数lt[j],右最近质数rt[j]
	}
	while(scanf("%d%d",&a,&b)!=EOF){
		if(rt[a]>=lt[b])printf("There are no adjacent primes.\n");
		else{
			mn=10000010,mx=0;
			for(i=pos[rt[a]];i<pos[lt[b]];i++){
				c=prime[i+1]-prime[i];
				if(c<mn)mn=c,d1=prime[i],d2=prime[i+1];
				if(c>mx)mx=c,d3=prime[i],d4=prime[i+1];
			}
			printf("%d,%d are closest, %d,%d are most distant.\n",d1,d2,d3,d4);	
		} 
	}
	return 0;
}

2.筛出区间内质数,运行超时代码0分

ybt

未通过

测试点结果内存时间
测试点1运行超时13756KB999MS

LOJ

突破口猜测还是在R−L≤10^6 

突然想到,该题的关键还是快速筛出给定区间[L,R]内的所有质数,有办法吗?

要筛出2,147,483,647是否是质数,只需检查能否被[1,sqrt(2,147,483,647)](即[1,46341])区间内的某个质数整除,

[1,46341]区间内的质数个数,

统计质数数量代码如下:

#include <bits/stdc++.h>
#define maxn 10000010
using namespace std;
int prime[maxn],tot=0,b[maxn];
void linear(int x){
	int i,j;
	for(i=2;i<=x;i++)b[i]=0;
	for(i=2;i<=x;i++){
		if(b[i]==0)prime[++tot]=i;
		for(j=1;j<=tot&&prime[j]*i<=x;j++){
			b[prime[j]*i]=1;
			if(i%prime[j]==0)break;
		}
	}
}
int main(){
	int n;
	scanf("%d",&n); 
	linear(n);
	printf("tot=%d\n",tot); 
	return 0;
}

对应的输入输出数据如下:

46341
tot=4792

R-L=10^6的极端条件下,极端计算次数10^6*4792=4.8*10^9,在没有好办法的情况下,可以一试。计算过程中,适当使用long long,避免数据溢出。

除了2以外的质数,都是奇数,自然数由奇数和偶数构成,那么R-L=10^6的极端条件下,极端计算次数,只考虑奇数的情况下,10^6*4792/2=2.4*10^9

准备尝试计算[2147483647-10^6+1,2147483647],即计算区间[2146483648,2147483647]内的奇数(假定这些奇数全是质数),需要判定这些奇数全是质数的计算次数。

sqrt(2146483649)=46331,sqrt(2147483647)=46341,[1,46341]区间内的质数个数4792,计算次数接近10^6*4792/2=2.4*10^9,那真实计算次数,是多少,那就编码来体验吧。

#include <bits/stdc++.h>
#define maxn 10000010
#define LL long long
using namespace std;
int prime[maxn],tot=0,b[maxn];
LL cnt=0;
void linear(int x){
	int i,j;
	for(i=2;i<=x;i++)b[i]=0;
	for(i=2;i<=x;i++){
		if(b[i]==0)prime[++tot]=i;
		for(j=1;j<=tot&&prime[j]*i<=x;j++){
			b[prime[j]*i]=1;
			if(i%prime[j]==0)break;
		}
	}
}
int main(){
	int n,j,a,b,mn,mx,c,d1,d2,d3,d4;
	LL i;
	linear(46341);//46341=sqrt(2147483647) 
	//[2146483648,2147483647]
	for(i=2146483649;i<=2147483647;i+=2)
		for(j=1;j<=tot&&(LL)prime[j]*prime[j]<=2147483647;j++){
			cnt++;
			if(i%prime[j]==0)break;
		}
	printf("cnt=%lld\n",cnt);
	return 0;
}

上述编码对应的输入输出数据如下:

cnt=273481050

2.7*10^8超时无疑,试着编编吧。

同时准备上STL map容器,避免质数,合数的重复查找,以应对多组测试数据。

样例通过,筛出区间内质数,运行超时代码0分 如下:

#include <bits/stdc++.h>
#define maxn 1000010
#define LL long long
using namespace std;
int prime[maxn],tot=0,b[maxn],p[maxn],pn=0;//p[]记录区间内的质数 
map<int,int> mp;
void linear(int x){
	int i,j;
	for(i=2;i<=x;i++)b[i]=0;
	for(i=2;i<=x;i++){
		if(b[i]==0)prime[++tot]=i,mp[i]=1;
		for(j=1;j<=tot&&prime[j]*i<=x;j++){
			b[prime[j]*i]=1,mp[prime[j]*i]=2;
			if(i%prime[j]==0)break;
		}
	}
}
int main(){
	int n,j,lt,rt,first,second,mn,mx,c,d1,d2,d3,d4;
	LL i;
	linear(46341);//46341=sqrt(2147483647) 
	while(scanf("%d%d",&lt,&rt)!=EOF){
		pn=0;
		if(lt<=2)p[++pn]=2,lt=3;
		if(lt%2==0)lt+=1;//让lt变成奇数 
		if(rt%2==0)rt-=1;//让rt变成奇数
		for(i=lt;i<=rt;i+=2){//筛出[lt,rt]之间的质数 
			if(mp[i]==0){
				for(j=1;j<=tot&&(LL)prime[j]*prime[j]<=i;j++)
					if(i%prime[j]==0)break;
				if(i%prime[j]==0)mp[i]=2;//i是合数 
				else mp[i]=1,p[++pn]=i;//i是质数 
			}else if(mp[i]==1)p[++pn]=i;
		}
		if(pn<2)printf("There are no adjacent primes.\n");
		else{
			mn=1000010,mx=0;
			for(j=1;j<pn;j++){
				c=prime[j+1]-prime[j];
				if(c<mn)mn=c,d1=prime[j],d2=prime[j+1];
				if(c>mx)mx=c,d3=prime[j],d4=prime[j+1];
			}
			printf("%d,%d are closest, %d,%d are most distant.\n",d1,d2,d3,d4);
		} 
	}
	return 0;
}

3.求任意区间质数的筛子100分

ybt

通过

测试点结果内存时间
测试点1答案正确50304KB927MS

LOJ

以[51,100]区间内找质数,演示求任意区间质数的筛子

找出[51,100]区间内的质数,
需要准备的质数范围[1,sqrt(100)],即[1,10],
该区间的质数是2,3,5,7可用普通的线性筛获得,
[51,100]内的合数,只会包含2,3,5,7这4个质因数,

目标,找出[51,100]内的所有合数,那么剩下的就是质数了。

针对质因数2,
[51/2,100/2]对应区间[25,50],制造出合数
25*2=50,26*2=52,27*2=54,28*2=56,29*2=58,30*2=60,31*2=62,32*2=64,33*2=66,
34*2=68,35*2=70,36*2=72,37*2=74,38*2=76,39*2=78,40*2=80,41*2=82,42*2=84,
43*2=86,44*2=88,45*2=90,46*2=92,47*2=94,48*2=96,49*2=98,50*2=100

针对质因数3,
[51/3,100/3]对应区间[17,33],制造出合数
17*3=51,18*3=54,19*3=57,20*3=60,21*3=63,22*3=66,23*3=63, 24*3=72, 25*3=75,
26*3=78, 27*3=81, 28*3=84, 29*3=87, 30*3=90, 31*3=93, 32*3=96, 33*3=99

针对质因数5,
[51/5,100/5]对应区间[10,20],制造出合数
10*5=50, 11*5=55, 12*5=60, 13*5=65, 14*5=70, 15*5=75, 16*5=80, 17*5=85, 18*5=90,
19*5=95, 20*5=100

针对质因数7,
[51/7,100/7]对应区间[7,14],制造出合数
7*7=49, 8*7=56, 9*7=63, 10*7=70, 11*7=77, 12*7=84, 13*7=91, 14*7=98

[51,100]之间的数 ()内的数,表示将其筛出的质因数
51(3) 52(2) 53 54(2,3) 55(5) 56(2) 57(3) 58(2) 59 
60(2,3) 61 62(2) 63(3) 64(2) 65(5) 66(2,3) 67 68(2) 69(3) 
70(2,5) 71 72(2,3) 73 74(2) 75(5) 76(2) 77(7) 78(2,3) 79 
80(2,5) 81(3) 82(2) 83 84(2,7) 85(5) 86(2) 87(3) 88(2) 89 90(2,3,5) 
91 92(2) 93(3) 94(2) 95(5) 96(2,3) 97 98(2) 99(3) 100(5)

除了合数,剩下的就是质数了
53 59 61 67 71 73 79 83 89 91 97

目前为止,意识到了,编程世界里,乘法(3.求任意区间质数的筛子100分)比除法(2.筛出区间内质数,运行超时代码0分)好

还是引入STL map容器,对应较大数据(开数组要爆内存),

提供一组边界样例

输入:
1 2

输出:
There are no adjacent primes.

求任意区间质数的筛子100分 代码如下:

#include <bits/stdc++.h>
#define maxn 1000010
#define LL long long
using namespace std;
int prime[maxn],tot=0,b[maxn],p[maxn],pn=0;//p[]记录区间内的质数 
map<int,int> mp;
void linear(int x){
	int i,j;
	for(i=2;i<=x;i++)b[i]=0;
	for(i=2;i<=x;i++){
		if(b[i]==0)prime[++tot]=i;
		for(j=1;j<=tot&&prime[j]*i<=x;j++){
			b[prime[j]*i]=1,mp[prime[j]*i]=2;
			if(i%prime[j]==0)break;
		}
	}
}
int main(){
	int n,j,lt,rt,first,second,mn,mx,c,d1,d2,d3,d4;
	LL i;
	linear(46341);//46341=sqrt(2147483647) 
	while(scanf("%d%d",&lt,&rt)!=EOF){
		pn=0;
		if(lt<2)lt=2;//左边界处理 
		for(i=1;i<=tot&&(LL)prime[i]*prime[i]<=rt;i++)
			for(j=lt/prime[i];j<=rt/prime[i];j++)
				if(j>1)mp[j*prime[i]]=2;//=2表示该数是合数 
		for(i=lt;i<=rt;i++)
			if(mp[i]==0)p[++pn]=i;
		if(pn<2)printf("There are no adjacent primes.\n");
		else{
			mn=1000010,mx=0;
			for(j=1;j<pn;j++){
				c=p[j+1]-p[j];
				if(c<mn)mn=c,d1=p[j],d2=p[j+1];
				if(c>mx)mx=c,d3=p[j],d4=p[j+1];
			}
			printf("%d,%d are closest, %d,%d are most distant.\n",d1,d2,d3,d4);
		} 
	}
	return 0;
}

比较好奇,极端条件下的计算次数,编码如下:

准备尝试计算[2147483647-10^6+1,2147483647],即计算区间[2146483648,2147483647]内的计算次数

#include <bits/stdc++.h>
#define maxn 1000010
#define LL long long
using namespace std;
int prime[maxn],tot=0,b[maxn],p[maxn],pn=0;//p[]记录区间内的质数 
map<int,int> mp;
void linear(int x){
	int i,j;
	for(i=2;i<=x;i++)b[i]=0;
	for(i=2;i<=x;i++){
		if(b[i]==0)prime[++tot]=i;
		for(j=1;j<=tot&&prime[j]*i<=x;j++){
			b[prime[j]*i]=1,mp[prime[j]*i]=2;
			if(i%prime[j]==0)break;
		}
	}
}
int main(){
	int n,j,lt,rt,first,second,mn,mx,c,d1,d2,d3,d4;
	LL i,cnt=0;
	linear(46341);//46341=sqrt(2147483647) 
	scanf("%d%d",&lt,&rt);
	for(i=1;i<=tot&&(LL)prime[i]*prime[i]<=rt;i++)
		cnt+=(rt-lt+1)/prime[i];
	printf("cnt=%lld\n",cnt);
	return 0;
}

上述代码对应的输入输出数据如下:

2146483648 2147483647
cnt=2633780

计算次数2.6*10^6断定极端条件下的测试数据,不会超过10组。

提高+/省选- 教会我什么? 线性筛不要固化,可以拓展拓展,求某一区间的合数,进而求得质数

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值