算法分析
二次筛法
这里的区间范围给定的最大值是2^31 - 1
,而用线性筛法求的是[1,n]中的所有质数,因此直接用线性筛法求肯定会直接gg,因此需要通过挖掘某些性质,才能有技巧性的完成,y总的视频讲得实在太好了,不得不为他点赞
性质
-
性质1:若一个数
n
是一个合数,必然存在2
个因子$d$,$\frac{n}{d}$,假设$d$ <= $\frac{n}{d}$,则$d$ <= $\sqrt n$,因此必然存在一个小于等于 $\sqrt n$的因子 -
性质2:若x$\in$[L,R],且x是合数,则一定存在P <= $\sqrt{2^{31}-1}$ (< 50000),使得
P
能整除x
,其中P < x
.
步骤
- 1、找出1 ~ $\sqrt{2^{31}-1}$ (< 50000)中的所有质因子
- 2、对于1 ~ 50000 中每个质数
P
,将[L,R]
中所有P
的倍数筛掉(至少2
倍)- 找到大于等于
L
的最小的P
的倍数$P_{0}$,找下一个倍数时只需要+= P
即可
- 找到大于等于
引理(分数的上取整转换下取整)
细节
ly20 同学在y总的代码 问的3个很高质量的问题,也是很细节的地方,感谢ly20 同学
- (1)每个质数是
2~50000
中的数,为啥LL p = primes[i];
这里的p
的LL
啊,int
不就够了吗? - (2)
for (LL j = max(p * 2, (l + p - 1) / p * p); j <= r; j += p)
这里的j
是小于等于r
的,而r
的取值范围是小于2 ^ 31
,也是在int
范围内啊, 这里为啥用LL
啊? - (3) 我知道这里是复用
st
数组,但是在用之前都初始化为0
了啊,为什么init(50000);
放在while (cin >> l >> r)
的外面(也就是最前面)代码不行啊?
回答:
- (1)
LL p = primes[i]
,这里p
用LL
是因为如果p
也是用int
类型,本身l
也是用int
类型,如果l
取得足够大,下面的l + p - 1
会有可能直接爆int
变成负数 - (2)这里的
j
是小于等于r
的,而r
的取值范围是小于2 ^ 31
,这里确实是这样,可是这个循环跳出的条件是j <= r
,也就是说如果r
是最大的int
,那么当j += p
,要超过最大的int
的时候需要比它还大才能跳出循环,因此直接爆int
变成负数,然后j <= r
依然成立,会一直死循环下去 - (3)放在最前面也是可以的,y总的代码中判断从
[1,50000]
中谁是质数和在区间[L,R]
中谁是质数直接复用st[]
数组,就不用再开一个数组去存了,也可以把init()
放在前面,用一个专门的数组去记录区间[L,R]
中谁是质数
时间复杂度 $O(n)$
参考文献
算法提高课
Java 代码
import java.util.Arrays;
import java.util.Scanner;
public class Main {
static int N = 50010;
static int M = 1000010;
static int[] primes = new int[N];
static int cnt = 0;
static boolean[] st = new boolean[N];
static boolean[] bool = new boolean[M];//存[L,R]的元素是否是质数
static int[] primes2 = new int[M];
static void init(int n)
{
for(int i = 2;i <= n;i ++)
{
if(!st[i]) primes[cnt ++] = i;
for(int j = 0;primes[j] * i <= n;j ++)
{
st[primes[j] * i] = true;
if(i % primes[j] == 0) break;
}
}
}
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
init(50000);
while(scan.hasNext())
{
int l = scan.nextInt();
int r = scan.nextInt();
Arrays.fill(bool, false);
Arrays.fill(primes2, 0);
for(int i = 0;i < cnt;i ++)
{
long p = primes[i];
for(long j = Math.max(2 * p, (l + p - 1) / p * p);j <= r;j += p)
bool[(int)(j - l)] = true;
}
int k = 0;
for(int i = 0;i <= r - l;i ++)
{
if(!bool[i] && i + l >= 2)
primes2[k ++] = i + l;
}
if(k < 2) System.out.println("There are no adjacent primes.");
else
{
int minp = 0,maxp = 0;//最小、大距离的位置
for(int i = 0;i + 1 < k;i ++)
{
int d = primes2[i + 1] - primes2[i];
if(d < primes2[minp + 1] - primes2[minp]) minp = i;
if(d > primes2[maxp + 1] - primes2[maxp]) maxp = i;
}
System.out.println(primes2[minp] + "," + primes2[minp + 1] + " are closest, "+ primes2[maxp] +"," + primes2[maxp + 1] + " are most distant.");
}
}
}
}
想问一下
(l + p - 1) / p * p)
这个为啥要这样写不能这样写吗
感觉那样写理解起来要麻烦很多耶
y20同学问的意思是init(50000)放到while循环外面为什么答案就出错了
因为一旦init放到外面 cnt就会因为多组测试数据而继承上一组测试数据的cnt值,导致下一组数据在第一处用到cnt的时候(第一处用到cnt是筛出区间的非素数,cnt的范围出错,导致素数筛错)而出错。
请问
i + l >= 2
这个条件是什么意思这里就是考虑边界情况,如果l是1的话,1既不是质数也不是合数,因为2是最小的质数因子,所以后续筛出的数都会大于等于4,更新数组下标为true。而1这个数从始至终没有访问,bool数组下标一直为false,当i=0,l=1时,不加这个判定条件的话,这里会把1这个数也添加进prime2质数数组,从而影响结果
区间筛的复杂度怎么感觉是$O(nlog(log))$的
大佬我有个疑问,两个性质:
其中并没有要求 d是质数啊,也可能是合数,为啥要求50000以内的质数而不是合数呢?
算术基本定理:任何一个大于1的自然数$N$,如果$N$不为质数 ,那么$N$可以唯一分解成有限个质数的乘积 $N={p_1^{a_1}}∗{p_2^{a_2}}*…∗{p_n^{a_n}}$
如果dd不为质数一定可以把dd变成一个更小的质数,所以可以只考虑用50000以内质数进行二次筛
大佬们,为什么根号2e31-1才小于五万
2e31-1等价于2.1 * 10的9次方, 2.5 * 10的9次方是五万
%%%
%%%
max(2p, 。。) 为啥药油2p
?
因为(l + p - 1)/p可能等于p,然后错误地标记st[i] = true,这样就把一个质数p错误地标记成了合数,所以至少要从2*p开始
太感谢了!
虽然是java,但是题解好评
谢谢hh
每次俺都看小呆呆的题解🤭太棒了
谢谢观看hhh
orz