思路:
- 题目给定的是区间 [l, r],让你筛选出里面所有满足性质的质数,区间里既有质数又有合数,假如只有质数,则可以直接进行暴力枚举,因为区间的长度也就一百万级别。
- 但是,由于区间既有质数又有合数,所以我们需要筛掉区间里面的合数,进行标记删除。
- 那么如何删除合数呢?根据算术基本定理可知:任何一个大于等于0的正整数都可以表示成若干个质数的乘积形式。所以说,筛去合数,我们选择使用质数去标记它在 [l, r]里的倍数。
- 而我们所学的筛质数是从 [1, n]的,并不能按照区间去筛,所以应该从 1 ~ n 去筛质数,但是,本题的n即为 r,r 最大取值为:231-1。所以会超时,应该想办法缩减筛质数的区间。
- 对于区间里每个合数,我们是需要它的因子去判断它,所以如果我们只记录它的最小的一个因子不就可以了,然后又由(3)可知,用质数去筛区间内的合数,所以组合起来就是最小质因子,所以我们应该缩减最小质因子的区间。将合法范围的最小质因子存储起来,然后去筛区间内的合数。
- 缩减质数的区间:重要性质:对于任何一个合数而言,一个合数的最小质因子,必定是小于等于该合数开根号。所以区间里面的每个数而言,其最小质因子也是小于等于对应元素的开根号。 该性质常常可以将O(n)的枚举转化为:O(根号n) 的枚举。由于我们的 n ,即区间右端点,最大取到
231-1,所以说对于 r 而言,必然存在一个 最小质因数:小于等于 r开根号。而 231-1开根号的数量级大概是 4万多,所以为了保险起见,我们只需要求出 1~5万的所有质数就行了。因为区间内的合数的最小质因子也必然在该范围内。
- 筛出所有的质数后,再枚举每个质数,用质数去筛区间里的每个合数。但是,你怎么直到里面的合数是当前质数的多少倍呢?你只能确定你有里面合数的最小质因子。所以我们需要去枚举每个质数,对于一个质数p,它从几倍开始枚举,取决于它大于等于L的最小的p的倍数。所以我们可以先看L是p的几倍,若能整除,则从L/p倍开始,第一个筛的就是L。若不能整除,则只需要往上加一倍就可以啦,即向上取整,而C++里面默认是向下取整,所以这里我们应该是采用公式:{L+p-1/p * p};开始枚举,直到质数 * 对应的倍数 > n为止。
- 然后通过枚举区间里没有被标记的质数,将所有的质数都存到一个数组里面去,然后开始两两枚举最小差值,最大差值。
草稿:(不用看,舍不得删除!)
本题给定的区间长度最大为1e6,即为U-L的最大长度!但是U和L的取值可以很大为:231-1,由于涉及到筛质数的操作,所以我们不可能筛到231-1,筛法是从2~n筛,所以说会超时。而我们应该抓住一个核心的性质:任何一个合数N都存在一个小于等于其根号N的因子存在,(证明如图一所示),而对于一个数,假设U=231-1,那么它也必然存在一个因子,该因子的范围是:<1, 231/2> == <1, 215>大概十万左右。那么我们可以将1 ~ 1e5内的所有质数筛选出来,此时对于区间 [L, U]里的任何一个合数而言,都必然有一个因子是小于等于1e5的,因为其最大取值的因子都小于等于1e5了。因此对于[L, U]里面的任何一个合数都至少存在一个质因子在[1, 1e5] 之间。
- 先预处理出来5e4以内的质数筛选出来。
- 然后利用筛选出来的质数,将[l, r]区间内的所有合数筛去,因为如果区间[l, r]里面有合数的话,那么其质因子的范围必然在[1, 5e4]之间,由于区间右端点 r 的最大值为 231-1,对于 231-1其必然也存在一个小于等于 根号(231-1)的因子,范围在5e4内,那么对于其区间内的数的质因子也必然是在5e4以内了。
- 时间复杂度的分析:假设 [l, r]的范围是1e6的话,而p的倍数最多是:[区间长度1e6]/p;而每个质数都要枚举一遍,那么整个加起来的时间复杂度:1e6*(1/2 + 1/3 + 1/5 + 1/7 + …+1/n1/2);大约整个加起来是:所以括号内的时间复杂度为log(log(n1/2))级别的,该时间复杂度也在埃氏筛法里面出现过!
- 所以总的来看:1e6 * 3 = O(n*loglog(n1/2));
- 常用性质:一个数的最小质因子一定是 小于等于n1/2从而将O(n)变为O(n1/2);
总结:
技巧:C++里面两个数相除上取整:
- 采用函数ceil:
- 采用公式:比如找一个数:该倍数必须是大于等L的最小的p的倍数,那么先用L/p算出至少多少倍,然后往上取整:[(L/p)*p]上取整 = [(L+p-1)/p] * p;
1.当L是p的倍数时,+p-1相当于没加。和之前相等,
2.当L不是p的倍数时,L%p >= 1,再加上一个p-1,必然会多一。
代码:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e6 + 10;
typedef long long LL;
int primes[N]; //存储质数数组
bool st[N]; //标记是否是合数,为真的话就是合数!
int cnt;
void get_primes(int n)
{
memset(st, 0, sizeof st);
cnt=0;
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; //保证每个合数只会被它的最小质因子筛去。
}
}
}
int main()
{
int l, r;
while (cin >> l >> r)
{
get_primes(50000);
memset (st, 0, sizeof (st)); //因为后续用来标记删除区间里的合数。
//既然质数已经预处理出来了,那么就开始筛去区间里面的合数:
for (int i=0; i < cnt; i ++)
{
LL p = primes[i];
//取max的原因是防止出现p。p是p的倍数,但不在[l,r]范围内?
for (LL j = max((l+p-1)/p*p, 2*p); j <= r; j += p) //枚举质数p的各个倍数!
{
st[j-l] = true; //设置偏移量,因为l过于庞大了,数组下标可能取不到。
}
}
cnt = 0; //重置,因为预处理的primes数组已经用完了,可以丢了!存储区间[l,r]内的所有质数
for (int i=0; i <= r-l; i ++)
{
if (!st[i] && i+l>=2) //如果这个数是质数的话,且不为1,因为1既不是质数也不是合数
primes[cnt ++ ] = i + l;
}
if (cnt < 2) puts("There are no adjacent primes.");
else
{
int minp = 0, maxp = 0;
for (int i = 0; i + 1 < cnt; i ++ )
{
int d = primes[i + 1] - primes[i];
if (d < primes[minp + 1] - primes[minp]) minp = i;
if (d > primes[maxp + 1] - primes[maxp]) maxp = i;
}
printf("%d,%d are closest, %d,%d are most distant.\n",
primes[minp], primes[minp + 1],
primes[maxp], primes[maxp + 1]);
}
}
return 0;
}