先介绍一下伟大的发明人:
ZZX,就读于广东省中山市纪念中学。
曾获得多个奖项,是一个超强的OI选手。
发明了两个改变世界的重要算法:詹筛和詹增
先说以前的筛法是发么打的。
一开始,我们打的都是埃氏筛法。就是从前往后扫,遇到一个素数,就筛去它的倍数,打上标记。没有打上标记的就是素数。
这个算法自然是很慢的,据说时间复杂度是O(nlnn)O(n \ln n)O(nlnn)。
于是我们就引进了欧拉筛法,也就是线筛,那么我们可以保证对于每个数,它们至多被筛一次,所以时间复杂度是O(n)O(n)O(n)的。
线筛比前面的算法优秀很多,所以一般情况下,我们都会选择打线筛。
可是,到此为止了吗?
不,我们还有詹筛。
其实詹筛的时间复杂度并不优秀,它不是线性的。
本质上它就是埃氏筛法的优化版本。
当n=108n=10^8n=108的时候,在JZOJ上评测,詹筛比线筛快了大概50ms。
如果这个nnn可以大些,詹筛或许不如线筛,可是,你还能再大多少吗?在OI比赛当中,你的空间有多少,你的时间有多少?所以在OI比赛的时间和空间范围限制中,詹筛是撵爆线筛的。
詹筛的思想其实很简单,就是在埃氏筛法当中,把222给拎出来单独考虑。
显然除了222以外,其它的偶数都不是合数。在埃氏筛法的处理当中,与222有关的东西其实很多。
于是就有了下面这个代码:
for (int i=3;i*i<=n;i+=2)
if (!inp[i])
for (int j=i*i;j<=n;j+=i<<1)
inp[j]=1;
是不是特别短?
这样我们就可以将标记数组给求出来,当然,这不包括偶数,在素数判定的时候我们要特判一下就好了。
詹筛是不需要将素数表打出来的,如果要打出来,扫一遍就好了。
或许不会比线筛优化多少,但是詹筛的代码太简洁了,看起来比线筛舒服很多。
欧拉:我不服!量你个毛头小子,居然敢撵爆欧拉筛法。
ZZX:行行行,我们结合起来好不好?
于是有了下面的代码:
p[*p=1]=2;
for (int i=3;i<=n;i+=2){
if (!inp[i])
p[++*p]=i;
for (int j=2;j<=*p && i*p[j]<=n;++j){
inp[i*p[j]]=1;
if (!(i%p[j]))
break;
}
}
这样就比线筛和原来的詹筛快多了哈哈哈……
于是我们起名叫詹欧筛法。
欧拉笑嘻嘻地回到他的天堂去了,原先的詹筛就算是吸氧气都比不上詹欧筛法(处理素数表的情况下)!
有一句话说得好:优化永无止境!
所以说还可以再优化吗?吸一口臭氧
不只是考虑222,也考虑333?
这似乎是一种不错的思路,但代码复杂度一定大很多,况且计算机是二进制的,用333可能会造成一些不好的常数影响。
其实有一种比较好的优化:
我们只需要存奇数,偶数的标记数组是可以不需要的。
那么就变成这样:
p[*p=1]=2;
for (int i=3;i<=n;i+=2){
if (!inp[i>>1])
p[++*p]=i;
for (int j=2;j<=*p && i*p[j]<=n;++j){
inp[i*p[j]>>1]=1;
if (!(i%p[j]))
break;
}
}
这样可以将标记数组的空间减半,并且时间上优化约40ms。
不过在有些时候我们利用筛法求的东西不只是素数,可能还有什么μ\muμ和ϕ\phiϕ之类的。
下面是求最小质因数的例子。
mp[2]=2,p[*p=1]=2;
for (int i=3;i<=n;i+=2){
if (!mp[i])
mp[i]=i,p[++*p]=i;
for (int j=2;j<=mp[i] && i*p[j]<=n;++j)
mp[i*p[j]]=j;
}
这个程序比上面的周筛法(连读)慢,但是它可以求出最小的质因数。
至于其它的……那就不在这里说明了。