Miller-Rabin强伪素数


16

给定一个非负整数N,将最小的奇数正整数输出为所有第一个N素数基数的强伪质数。

这是OEIS序列A014233

测试用例(一索引)

1       2047
2       1373653
3       25326001
4       3215031751
5       2152302898747
6       3474749660383
7       341550071728321
8       341550071728321
9       3825123056546413051
10      3825123056546413051
11      3825123056546413051
12      318665857834031151167461
13      3317044064679887385961981

的测试用例N > 13不可用,因为尚未找到这些值。如果您设法按顺序查找下一个术语,请务必将其提交给OEIS!

规则

  • 您可以选择采用N零索引或一索引的值。
  • 您的解决方案只适用于语言整数范围内的可表示值是可接受的(最多适用N = 12于无符号64位整数),但是理论上您的解决方案必须在假设您的语言支持任意长度整数的情况下适用于任何输入。

背景

任何正偶数x可以写成如下形式x = d*2^s,其中d为奇数。d并且s可以通过反复分割计算n由2,直到商是通过2不再整除d是最终商,并且s是的2所划分的数目n

如果一个正整数n是质数,那么费马小定理说明:

费马

在任何有限域 Z/pZ(其中p有一些素数)中,的唯一平方根11-1(或等价地为1p-1)。

我们可以使用这三个事实来证明以下两个语句之一对于素数必须为true n(其中d*2^s = n-1r是中的一些整数[0, s)):

Miller-Rabin条件

米勒罗宾素性测试操作通过测试上述权利要求的对换句:如果有一个基座a,使得上述两个条件都为假,则n不是素数。该基地a被称为见证人

现在,测试每个数据库[1, n)对于大的计算时间将是非常昂贵的n。Miller-Rabin检验有一个概率变体,仅测试有限域中的一些随机选择的碱基。但是,已经发现仅测试素数a碱基就足够了,因此可以以有效和确定性的方式进行测试。实际上,并非所有的素数基数都需要进行测试-仅需要一个特定的数,并且该数取决于被测试的素数的大小。

如果测试的素数数量不足,则该测试会产生假阳性-奇数复合整数,而该测试无法证明其复合性。具体来说,如果一个基数a不能证明奇数个复合数的合成性,则该数称为对基的强伪素a。这个挑战就是找到奇合数谁是强psuedoprimes到所有基地小于或等于N个素数(这相当于说,他们是强伪所有主要基地小于或等于N个质数) 。


1
沙盒帖子(现已删除)
Mego,

是否有一种算法可以测试所有从1开始的奇数值,以得出规则所允许的强伪素数?
user202729

@ user202729我不明白为什么不会。你会以为是什么?
Mego

我建议将其作为代码最快的问题,因为大多数答案将只是蛮力。
尼尔·A,

@NeilA。我不同意将其作为最快的代码会更好。的确答案几乎肯定是蛮力的(因为尚未开发另一种算法,而且我不希望PPCG这样做),但是代码高尔夫要简单得多,进入门槛要低得多(因为提交者可以给自己的解决方案打分),不需要我为每个解决方案打分和打分(并处理过高的运行时间),而且这个问题作为高尔夫挑战赛也很有趣。
Mego

Answers:


4

C,349个 295 277 267 255字节

N,i;__int128 n=2,b,o,l[999];P(m){i<N&&P(m<2?l[i++]=n:n%m?m-1:n++);}main(r){for(P(scanf("%d",&N));r|!b;)for(++n,b=i=N;i--&&b;){for(b=n-1,r=0;~b&1;b/=2)++r;for(o=1;b--;o=o*l[i]%n);for(b=o==1;r--;o=o*o%n)b|=o>n-2;for(o=r=1;++o<n;r&=n%o>0);}printf("%llu",n);}

在stdin上接受基于1的输入,例如:

echo "1" | ./millerRabin

当然,它不会很快发现序列中的任何新值,但是可以完成工作。更新:现在更慢!

  • 灵感来自Neil A的答案(a^(d*2^r) == (a^d)^(2^r)),速度稍快和短
  • 在意识到针对此挑战的所有解决方案将为奇数之后,速度又显着降低,因此无需明确要求我们仅检查奇数。
  • 现在使用GCC __int128,它比unsigned long long同时使用大数时还要短!同样在小端机上,printf %llu仍然可以正常工作。

较小的

N,i;
__int128 n=2,b,o,l[999];
P(m){i<N&&P(m<2?l[i++]=n:n%m?m-1:n++);}
main(r){
    for(P(scanf("%d",&N));r|!b;)
        for(++n,b=i=N;i--&&b;){
            for(b=n-1,r=0;~b&1;b/=2)++r;
            for(o=1;b--;o=o*l[i]%n);
            for(b=o==1;r--;o=o*o%n)b|=o>n-2;
            for(o=r=1;++o<n;r&=n%o>0);
        }
    printf("%llu",n);
}

(过时)细目分类

unsigned long long                  // Use the longest type available
n=2,N,b,i,d,p,o,                    // Globals (mostly share names with question)
l[999];                             // Primes to check (limited to 999, but if you
                                    // want it to be "unlimited", change to -1u)
m(){for(o=1;p--;o=o*l[i]%n);}       // Inefficiently calculates (l[i]^p) mod n

// I cannot possibly take credit for this amazing prime finder;
// See /codegolf//a/5818/8927
P(m){i<N&&P(m<2?l[i++]=n:n%m?m-1:n++);}

main(r){
    for(
        P(scanf("%llu",&N));        // Read & calculate desired number of primes
        r|!b;){                     // While we haven't got an answer:
        n=n+1|1;                    // Advance to next odd number
        for(b=1,i=N;i--&&b;){       // For each prime...
            for(d=n-1,r=0;~d&1;d/=2)++r; // Calculate d and r: d*2^r = n-1
            // Check if there exists any r such that a^(d*2^r) == -1 mod n
            // OR a^d == 1 mod n
            m(p=d);
            for(b=o==1;r--;b|=o==n-1)m(p=d<<r);
            // If neither of these exist, we have proven n is not prime,
            // and the outer loop will keep going (!b)
        }
        // Check if n is actually prime;
        // if it is, the outer loop will keep going (r)
        for(i=r=1;++i<n;r&=n%i!=0);
    }
    printf("%llu",n);               // We found a non-prime; print it & stop.
}

如前所述,它使用基于1的输入。 但是对于n = 0,它会产生9,该序列遵循相关序列https://oeis.org/A006945不再; 现在挂在0上。

应该对所有n都有效(至少直到输出达到2 ^ 64),但是速度却非常慢。我已经在n = 0,n = 1和(经过大量等待),n = 2上进行了验证。


我在解决方案上取得了突破,然后您一个人来...好!
尼尔·

@NeilA。抱歉! 在发布更新之前,我正在使用较短的int类型。我确定您会在某个地方找到2个字节;考虑到这是2种不同的非高尔夫语言,它具有令人惊讶的竞争力:D
Dave

3

Python 2中,633 465 435 292 282 275个 256 247字节

0索引

质疑您的实现并尝试一些新的东西

从函数转换为程序可以以某种方式节省一些字节...

如果Python 2为我提供了一种更短的方法来执行相同的操作,那么我将使用Python2。默认情况下,除法是整数,因此除以2的方法更简单,并且print不需要括号。

n=input()
f=i=3
z={2}
a=lambda:min([i%k for k in range(2,i)])
while n:
 if a():z|={i};n-=1
 i+=2
while f:
 i+=2;d,s,f=~-i,0,a()
 while~d&1:d/=2;s+=1
 for y in z:
  x=y**d%i
  if x-1:
   for _ in[]*s:
    x*=x
    if~x%i<1:break
   else:f=1
print i

在线尝试!

与其他语言相比,Python的速度非常慢。

定义绝对正确性的试验划分测试,然后重复应用Miller-Rabin测试,直到找到伪素数。

在线尝试!

编辑:最后打出答案

编辑:用于min审判部门的原始性测试,并将其更改为lambda。效率较低,但较短。也无法帮助自己,并使用了几个按位运算符(无长度差)。理论上,它应该(稍微)更快地工作。

编辑:感谢@Dave。我的编辑把我拖了。我以为我正在使用制表符,但它却被转换为4个空格。还几乎遍历了每个Python技巧并加以应用。

编辑:切换到0索引,允许我保存与生成素数的几个字节。还重新考虑了一些比较

编辑:使用变量来存储测试的结果,而不是for/else语句。

编辑:移动lambda函数内部以消除对参数的需要。

编辑:转换为一个程序以保存字节

编辑:Python 2为我节省了字节!另外我也不必将输入转换为int


为您的处理方式+1 a^(d*2^r) mod n
戴夫

您知道吗,您可以在Python中使用单空格(或单标签)缩进来保存…实际上是很多字节
Dave

@Dave:每个缩进级别使用1个制表符
Neil A.

我认为您的IDE会让您感到困惑,并在告诉您使用制表符的同时节省了空间;当我将它们替换为单个空格时,我得到的字节数仅为311个字节!在线尝试!
戴夫

@Dave:好的,很奇怪,谢谢,我将更新答案。
尼尔·A

2

Perl + Math :: Prime :: Util,81 + 27 = 108字节

1 until!is_provable_prime(++$\)&&is_strong_pseudoprime($\,2..nth_prime($_));$_=""

运行-lpMMath::Prime::Util=:all(27字节罚款,哎哟)。

说明

不仅仅是Mathematica具有内置的基本功能。Perl拥有CPAN,这是最早的大型图书馆存储库之一,并且为此类任务提供了大量现成的解决方案。不幸的是,默认情况下不会导入(甚至未安装)它们,这意味着在使用它们基本上不是一个好选择,但是当其中之一恰好适合问题时……

我们对连续的整数进行遍历,直到找到一个不是素数的整数,而且对于从2到第n个素数的所有整数基数都具有很强的伪素数。命令行选项导入包含有问题的内置函数的库,并且还设置隐式输入(一次一行;Math::Prime::Util具有自己的内置bignum库,该库不喜欢其整数中的换行符)。这使用了标准Perl技巧,即使用$\(输出行分隔符)作为变量,以减少笨拙的解析并允许隐式生成输出。

请注意,我们需要在is_provable_prime这里使用请求确定性而不是概率性的主要检验。(特别是考虑到可能首先使用Miller-Rabin进行概率素数测试,在这种情况下我们不能期望给出可靠的结果!)

Perl + Math :: Prime :: Util,71 + 17 = 88字节,与@Dada合作

1until!is_provable_prime(++$\)&is_strong_pseudoprime$\,2..n‌​th_prime$_}{

运行-lpMntheory=:all(17个字节的罚款)。

这使用了一些我不知道的Perl高尔夫技巧(显然Math :: Prime :: Util有一个缩写!),知道但没有想到使用(隐式}{输出$\一次,而不是"$_$\"隐式输出每一行) ,或知道但以某种方式设法应用错误(从函数调用中删除括号)。感谢@Dada向我指出这些。除此之外,它是相同的。


当然,打高尔夫的语言来了,而且胜过了其他语言。做得好!
尼尔A.17年

您可以使用ntheory代替Math::Prime::Util。另外,}{代替;$_=""应该没问题。您可以省略1一些函数调用后的空格和括号。另外,也&可以代替&&。那应该提供88个字节:perl -Mntheory=:all -lpe '1until!is_provable_prime(++$\)&is_strong_pseudoprime$\,2..nth_prime$_}{'
Dada

我完全忘记了}{。(奇怪的是,我想起了括号,但自从我在Perl打高尔夫球以来已经有一段时间了,不记得将其遗忘的规则了。)不过,我完全不知道ntheory缩写。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.