比较严谨的解释算法思想
问题
给定区间[M,N],找出区间中的素数个数。输入第一行为数据的组数C,以下C行中的每一行输入区间端点。
切入点
首先,你应该知道一个合数可以拆分为多个小于它的素数的乘积。而一个拆分后包含某一个素数的合数,一定可以通过这个素数和大于等于2的自然数相乘得到,同样这个素数和大于等于2的自然数相乘可以得到所有能够拆分得到这个素数的合数。证明也很简单,你只需要思考让合数除以这个素数得到的那个数就是一个大于等于2的自然数。
算法思想
我们对于每一个素数乘上大于等于2的自然数,就可以把所有的合数排除完毕,你可能会想到我并不知道所有的素数啊?thinking about it…在切入点黄色强调部分讲过,小于它的素数,这样就使得我们可以按照自然数列进行遍历,如果当前为素数,那么我们计算小于等于N的所有能够拆分为当前素数的合数,并标记它,当我们再遍历下一个数的时候,如果此数未标记,那么就说明了此数不能够拆分为任何小于它的素数(反证法:假设可以拆分,那么一定存在小于它的素数乘上某一个自然数等于它,显然与我们前面做的运算矛盾),也就说明此数也是素数,需要计算所有可以拆分为此素数的小于等于N的合数。如果此数有标记那么此数就是合数,不需要进行合数计算。
Pseudocode
1<M<N<100000
//the 1st index with 1
initialize nums[100000] with false
for i=2,i*i<=100000,i++
if not nums[i]
for j=2,i*j<=100000,j++
nums[i*j] = true
end for
end if
end for
initialize count[100000] with 0
for i in [2,100000]
count[i] = count[i-1]
if not nums[i]
count[i]++
end if
end for
output count[N] - count[M-1]
这里解释一下,为什么我们只需要使用小于等于
⌊
100000
⌋
\lfloor \sqrt{100000} \rfloor
⌊100000⌋的素数计算0-100000的合数呢?因为对于某一个大于1的自然数
n
n
n,
a
∗
b
=
n
,
a
=
n
,
b
=
n
a*b = n,a=\sqrt{n},b=\sqrt{n}
a∗b=n,a=n,b=n
必然a和b中一个增大另一个一定减小,不可能存在同时增加或减小。假设我们增大a,那么显然b必定减小,那么我们可以得出所有
n
n
n的因数,因数里面必然包含它所能拆分的所有素数。
n
n
n一定存在小于等于
n
\sqrt{n}
n的因数,并且成对出现,那么现在我们要确定的是小于等于
n
\sqrt{n}
n的因数中是否会不存在素数?这样我们就没办法在平方根的范围内计算出所有的合数了。前面已经说明一个合数能够拆分为全是素数的乘积,所以这个情况是不存在的,其实利用反证法也可以得证。所以利用某一个合数的平方根的范围内的素数乘上某一个自然数能够得到这个合数。