我写了一些代码以蛮力反驳Euler命题:

$$ a ^ 4 + b ^ 4 + c ^ 4 = d ^ 4 $$

当\ $ a \ $,\ $ b \ $,\ $ c \ $,\ $ d \ $是正整数时没有解决方案。


I我不是数学家,但仔细阅读此书,Noam Elkies于1987年找到了至少一个解决方案(a = 95800; b = 217519; c = 414560; d = 422481)。我想知道用蛮力解决需要多少火力,所以我在C语言中写了以下内容:

#include <stdio.h>
#include <time.h>
#include <math.h>

int prop(long int A, long int B, long int C, long int D) {
    return (pow(A, 4) + pow(B, 4) + pow(C, 4) == pow(D, 4));
}

int main() {
    long int a, b, c, d;
    clock_t t;
    t = clock();

    for (a = 1; a < 100000; a++) {
        for (b = 1; b < 300000; b++) {
            for (c = 1; c < 500000; c++) {
                for (d = 1; d < 500000; d++) {
                    if (prop(a, b, c, d))
                        printf("FOUND IT!\na = %ld\nb = %ld\nc = %ld\nd = %ld\n", a, b, c, d);
                }
                if (!(c%1000))
                    printf("a = %ld, b = %ld, c = %ld, time = %fs\n", a, b, c, ((double)(clock() - t))/CLOCKS_PER_SEC);
            }
            printf("a = %ld, b = %ld, time = %fs\n", a, b, ((double)(clock() - t))/CLOCKS_PER_SEC);
        }
        printf("a = %ld, time = %fs\n", a, ((double)(clock() - t))/CLOCKS_PER_SEC);
    }

}


我运行了一段时间,然后算出,要在我当前的机器上达到上述答案,大约需要花费\ $ 85 \乘以10 ^ 6 \ $美元的时间。

我的意思是,也许我等了几千几年,我会拥有一台更好的机器,但是我的问题是(而且我是C和计算机科学的新手,所以请保持温柔);我可以采取什么策略使上述代码更快地运行?我考虑过线程处理,并使用某种形式的位移来代替pow()调用。 >

评论

您可以做的一件事是for(b = a; ... for(c = b;。例如,您不需要同时检查(a,b,c)=(1、2、3)和( a,b,c)=(3,2,1)。同样,对于给定的(a,b,c)的三个值的任何集合,检查d的所有值在1到500,000之间非常浪费。 d ^ 4通过将(a,b,c)的四次幂加起来,然后检查其第四根是否为整数,这将改善所需的绝对时间,但不会改善一般的时间复杂度。
通过研究答案中建议的改进,您可以学到很多东西-但是这些答案都无法将您不切实际的方法变成您可以实际用来查找Elkies的示例的方法。您不仅仅需要良好的编程。这就是为什么需要像Elkies这样的数学家来提出它的原因。
在这种情况下(〜1e20 ops),有一个更好的算法(更好的大O时间复杂度)比将实现方式提高一个恒定因子的简单实现技巧更重要(在这种情况下,甚至快100倍也太慢了)。 br />
b和c的范围内有100000 * 300000 * 500000或1.5e16的值。即使您可以在1纳秒内检查数字是否是完美的四次幂,也需要174年的时间才能探索整个搜索空间。因此,最重要的是要大幅度减少以某种方式搜索的候选人的数量。

@ diego.martinez:在此之前或之后的增量完全相等,任何体面的编译器都将生成相同的代码,包括如果编写的是a = a + 1而不是a ++

#1 楼

注意:在某些时候,此评论进入了汇编程序和GMP领域。这篇文章的末尾有实际的评论,而第一部分讨论了与pow有关的运行时问题,错误的数据类型和任意大整数。 br />
在我的一生中,有什么方法可以使它在我的一生中运行?一旦。它涉及到切割木材或其他材料,如果不小心在错误的地方切割,则必须浪费资源。

软件工程师也有类似的说法:您无法优化自己能做到的无法衡量。有几种方法可以衡量您的代码,例如进行基准测试,性能分析或查看生成的汇编程序,以查看您的代码的特定部分将执行多少条指令。一步,看看最终结果。

汇编研究

让我们看看您的代码。好吧,不是您的,而是编译器生成的汇编程序。您可以使用gcc -S -O3。在我的平台上,这导致main中的以下“热门”部分:您需要知道的第一件事是,与其他操作相比,pow速度较慢。这四个call发生在最内部的循环中。编译器将call删除为call,而是用其代码替换了它(更快)。

prop将值分配给寄存器,mov*添加值,依此类推。 add*的寄存器是双精度寄存器,用于xmm*变量。因此,我们基本上是使用正确的值调用double,然后添加,减去和修改小的double值。

双重麻烦

但是请稍等。我们正在尝试解决一个完全集成的问题!为什么我们生成的程序完全使用那些寄存器?

这应该引发一个红色标记。确实,如果我们记得pow的签名,则应该清楚这不是正确的工具。它采用双底数和指数,表示适用于\ $ 15.12151 ^ {3.1415926} \ $之类的字词。这完全是解决您问题的方法。

使用适当的功能

所以让我们改用另一个pow版本:

.L6:
        add     rbx, 1
        cmp     rbx, 500000
        je      .L18
.L8:
        mov     rax, QWORD PTR .LC0[rip]
        movsd   xmm0, QWORD PTR [rsp+40]
        movq    xmm1, rax
        call    pow                        ; (1)
        mov     rax, QWORD PTR .LC0[rip]
        movsd   QWORD PTR [rsp+8], xmm0
        movsd   xmm0, QWORD PTR [rsp+48]
        movq    xmm1, rax
        call    pow                        ; (2)
        mov     rax, QWORD PTR .LC0[rip]
        movsd   QWORD PTR [rsp+16], xmm0
        movsd   xmm0, QWORD PTR [rsp+32]
        movq    xmm1, rax
        call    pow                        ; (3)
        mov     rax, QWORD PTR .LC0[rip]
        movsd   QWORD PTR [rsp+24], xmm0
        pxor    xmm0, xmm0
        cvtsi2sd        xmm0, rbx
        movq    xmm1, rax
        call    pow                        ; (4)
        movsd   xmm2, QWORD PTR [rsp+8]
        addsd   xmm2, QWORD PTR [rsp+16]
        movapd  xmm1, xmm0
        movsd   xmm0, QWORD PTR [rsp+24]
        addsd   xmm0, xmm2
        ucomisd xmm0, xmm1
        jp      .L6
        jne     .L6


请注意,您的编译器应从中创建以下代码:

long int pow4(long int x){
    return x * x * x * x;
}


相反。

我们还需要更改pow

movq    %rdi, %rax
imulq   %rdi, %rax
imulq   %rax, %rax
ret


>好的。现在,在我展示新程序的时间之前,让我们检查一下旧程序的输出:使用prop的人如何忍受呢?但是,这要付出代价:pow4对于pow4(500000)太大,因为\ $ \ log_2(500000 ^ 4)\ approx 76 \ $。使用int64_t可以检查的最大数字是65535,\ $ 2 ^ {16} -1 \ $,这并不奇怪。由于该标准未提供uint64_t或类似的标准,因此应确保您的数字不超出这些范围。

您可以为此编写自己的大整数逻辑,也可以使用GMP。 />
适当的边界和参数估计

接下来,您可以增加int128_tb的下界,这样\ $ a \ le b \ le c \ $。对于c,如果我们有dab,那么对于c只有一个解决方案。我们可以通过二进制搜索直接搜索该解决方案。

二进制搜索从您当前的\ $ \ mathcal O(n ^ 4)\ $一个算法中生成\ $ \ mathcal O(n ^ 3 \ log n)\ $算法,该算法比以前的加速要快得多。

更好的是,如果对dab使用了适当的范围,我们可以通过
$ brd $ dd4 = a ^ 4 + b ^ 4 + c ^ 4 \ le c ^ 4 + c ^ 4 + c ^ 4 = 3c ^ 4 $$

,因此得到

$$ c \ le d \ le \ sqrt [4] {3} \,c。$$

使用适当的二进制算法,您可以快速完成第一个cd的情况:

long int pow4(long int x){
    const long int t = x * x;
    return t * t;
}


这使我们回到了您一生的境界。

锻炼

编写一个函数,给定的a=1b=1a检查是否存在b,以便您拥有财产。如果不存在这样的c,它将返回d,否则返回-1

在代码中使用该函数。确保在该函数中大约需要\ $ \ log d _ {\ text {max}} \ $次迭代。

关于整数大小的重要说明

请记住, d通常只是一个64位整数,这意味着您可以存储的最大整数是\ $ 2 ^ {63} -1 \ $。具有更多位的整数类型具有更大的界限,但特定于平台。另外,乘法可能会慢一点,因为乘以128位数字并不像乘以64位数字那么容易。

请参阅下一节如何降低乘法。

实际评论

我们的d现在基本上是两个乘法。但是,我们仍然经常使用long int。毕竟,我们不需要在每次迭代中都重新计算\ $ a ^ 4 \ $。编译器很乐意这样做,因为它没有进行足够积极的优化。

这使我们进入了实际审查:您的代码编写简洁,易于阅读和理解。不幸的是,除非您的编译器/运行时非常聪明(因此通常很昂贵),否则编写良好的模块化代码通常不会从硬件中挤出最后一点(嘿)。

所以让我们开始吧返回绘图板以最终查看您的代码:

包括

您不需要任何不必要的内容,也不会忘记某些东西(并由不符合标准的编译器保存)。

声明


根据您编写的是ANSI-C还是C99,我会尽可能推迟声明变量。例如,目前很容易将pow4意外更改为某些虚假值,或者忘记了pow4并在循环或类似事件后意外检查c

int prop(long int A, long int B, long int C, long int D) {
  return (pow4(A) + pow4(B) + pow4(C) == pow4(D));
}


{不会被检查,您也不会收到警告(在较旧的编译器版本中;新版本会警告可能的空白问题)。如果以后再声明变量(例如C99样式),则不会发生类似的错误(尽管它会引入阴影):

a = 1, b = 1, c = 1000, time = 114.156000s


这将导致编译器错误,因为propif等不在范围内。无论哪种方式,都取决于您要使用的语言标准。有些人喜欢一种方式,另一些则喜欢。选择您自己的类型。

类型

鉴于所有值均应严格大于零,因此a不合适,因为它可能为负数。我们应该适应这一点。但是,不要在整个代码中使用b,而是使用类型同义词,以防日后要将其更改为更大范围的类型时使用:

可能想出一个更好的名称。

缓存结果(手工)

最让我印象深刻的一件事是,您每次都会重新计算\ $ a ^ 4 \ $等等。我们可以轻松地使用更多变量(使用您的声明样式)来处理此问题:这是您不得不帮助编译器的不幸示例之一(除非您确切地知道必须使用哪些优化标志,否则编译器会过于激进)。 long int不见了,对long unsigned int的调用现在已在您的循环中。 500000次。

,我们应该应用其他建议,如类型同义词和后期声明:

a = 1, b = 1, c = 1000, time = 0.296000s
a = 1, b = 1, c = 2000, time = 0.578000s
a = 1, b = 1, c = 3000, time = 0.859000s
a = 1, b = 1, c = 4000, time = 1.140000s
a = 1, b = 1, c = 5000, time = 1.421000s
a = 1, b = 1, c = 6000, time = 1.703000s
a = 1, b = 1, c = 7000, time = 1.984000s
a = 1, b = 1, c = 8000, time = 2.265000s
a = 1, b = 1, c = 9000, time = 2.546000s
a = 1, b = 1, c = 10000, time = 2.828000s
a = 1, b = 1, c = 11000, time = 3.109000s
a = 1, b = 1, c = 12000, time = 3.390000s
a = 1, b = 1, c = 13000, time = 3.687000s
a = 1, b = 1, c = 14000, time = 3.968000s
a = 1, b = 1, c = 15000, time = 4.250000s
a = 1, b = 1, c = 16000, time = 4.531000s
这里没有必要,这将确保我们不会意外更改缓存的结果。

时间到了

尽管我们的代码现在更加冗长,但是一小段代码会在您的整个prop中重复三遍:

…
a = 1, b = 1, c = 481000, time = 0.031000s
a = 1, b = 1, c = 482000, time = 0.031000s
a = 1, b = 1, c = 483000, time = 0.031000s
a = 1, b = 1, c = 484000, time = 0.031000s
a = 1, b = 1, c = 485000, time = 0.031000s
a = 1, b = 1, c = 486000, time = 0.031000s
a = 1, b = 1, c = 487000, time = 0.031000s
a = 1, b = 1, c = 488000, time = 0.031000s
a = 1, b = 1, c = 489000, time = 0.031000s
a = 1, b = 1, c = 490000, time = 0.031000s
a = 1, b = 1, c = 491000, time = 0.031000s
a = 1, b = 1, c = 492000, time = 0.031000s
a = 1, b = 1, c = 493000, time = 0.031000s
a = 1, b = 1, c = 494000, time = 0.031000s
a = 1, b = 1, c = 495000, time = 0.031000s
a = 1, b = 1, c = 496000, time = 0.031000s
a = 1, b = 1, c = 497000, time = 0.031000s
a = 1, b = 1, c = 498000, time = 0.031000s
a = 1, b = 1, c = 499000, time = 0.031000s
a = 1, b = 1, time = 0.031000s


这很难读,不是吗?它是理想的功能候选者: />
#include <stdio.h>
#include <time.h>
#include <math.h>


啊。更容易阅读。这就是pow4函数的作用。请注意,无论如何,任何复杂的编译器都应内联该函数,因此,如果您不想使用C99,也可以删除a4

评论


\ $ \ begingroup \ $
积分pow4是完全合理的,但是您需要使用足够大的数据类型来保存结果!
\ $ \ endgroup \ $
– Ben Voigt
16-10-25在20:26

\ $ \ begingroup \ $
切换到整数运算存在一个主要问题:解决方案涉及数字31858749840007945920321,该数字甚至不能存储在64位int中。
\ $ \ endgroup \ $
–马克
16-10-25在21:23

\ $ \ begingroup \ $
@Mark:因此,它也不能存储在64位double中。
\ $ \ endgroup \ $
–埃里克·利珀特
16-10-25在22:45

\ $ \ begingroup \ $
@ymbirtt:GCC会将x * x * x * x优化为两个imul指令:pow4:movq%rdi,%rax; imulq%rdi,%rax; imulq%rax,%rax; ret(顺便说一下,这些内容稍后会再次内联)。因此,是的,编译器足够智能,可以进行优化:)。
\ $ \ endgroup \ $
– Zeta
16-10-26在8:34



\ $ \ begingroup \ $
还有一个更好的估计\ $ d \ $的上限,因为如果\ $ a <= b <= c \ $则\ $ a ^ 4 + b ^ 4 + c ^ 4 < = 3c ^ 4 \ $,因此\ $ d <= \ sqrt [4] {3} \ times c $,用c / 128 * 169可以很好地近似。
\ $ \ endgroup \ $
– Ext3h
16年11月4日在17:48



#2 楼

好吧,让我们从这里开始:

for (a = 1; a < 100000; a++) {
    for (b = 1; b < 300000; b++) {
        for (c = 1; c < 500000; c++) {


现在暂时忽略d。你在这里做什么?您先检查1,1,1,然后1,1,2,然后1,1,3,...直到1,1,499999。然后从1,2,1重新开始。但是您已经检查了1, 1,2,那么为什么要检查1,2,1?您可以直接转到1、2、2。对于这些低数字而言,这并不能为您节省太多,但是请相信我,当您获得大数字时,它加起来。

简而言之: b,c应该不减。我们可以通过在a处开始b并在b处开始c来实现这一点,因此b永远不会小于a,而c永远不会小于b。因此,您马上就可以摆脱大约一半的工作。

for (a = 1; a < 100000; a++) {
    for (b = a; b < 300000; b++) {
        for (c = b; c < 500000; c++) {


接下来,考虑d

当然,我们可以立即对a,b和c进行相同的优化,因为d永远不会小于任何一个,并且c始终是最大的。

另外,一旦d4大于总和,我们就可以停止增加d,因为它只会变得更大。

这样可以节省大量时间。但是我们可以做得更好。

您要问的问题是“这四个数字是否具有sum属性?”但是您应该问的问题是“ a4 + b4 + c4等于任何四次方吗?”如果是这样,那么您可以比尝试所有可能的四次方轻松地更快地计算d。因此,您可以编写一个快速的方法来告诉您某个特定的和是否是四次方吗?

如果您知道如何求平方根,您可以做的是求和,取平方根两次,然后对结果求平方两次。如果您取回原始和,则它是四次方,否则,则不是。

如果您不知道如何求平方根,则可以执行以下逻辑: 1284等于和吗?不,更大吗?是。然后再尝试644。等于和吗?否。较小吗?是。然后尝试964,依此类推。二进制搜索结果,直到找到它,或者直到您的范围中没有更多的数字可检查为止。与尝试成千上万种可能的四次幂相比,这仅进行了很少的计算。

评论


\ $ \ begingroup \ $
哪个会更快?二进制搜索还是您首先建议的sqrt(sqrt(pow(pow(x,2),2)))?我想答案是我既要实现又要看到。好答案。谢啦。
\ $ \ endgroup \ $
–́Aidenhjj
16-10-25在19:14

\ $ \ begingroup \ $
@Aidenhjj:Eric不是建议sqrt(sqrt(pow(pow(x,2),2))),而是pow(pow(sqrt(sqrt(x)),2),2)。顺序非常重要。
\ $ \ endgroup \ $
– Ben Voigt
16-10-25在20:22

\ $ \ begingroup \ $
一个sqrt实现可能在内部使用二进制搜索,因此,用两个sqrt代替一个二进制搜索可能不会取得成功。
\ $ \ endgroup \ $
– 5gon12eder
16-10-26在0:12

\ $ \ begingroup \ $
而不是二进制搜索通常四次幂等于a⁴+b⁴+c⁴之和的广告,可能值得记住的是d的值刚好大于和(称为dlast),然后下次在c循环时检查它是否等于和(win!),是否等于或大于(单独放置),或者小于等于和(在这种情况下,加1再试一次)。那应该只需要在内循环中进行一两次尝试(而不是O(log(n)))
\ $ \ endgroup \ $
–马丁·邦纳(Martin Bonner)支持莫妮卡(Monica)
16-10-26在10:35

\ $ \ begingroup \ $
另一条评论:可能值得存储a4 == pow4(a)和a4b4 == a4 + pow4(b)。仍然需要对c和d的pow4进行O(n³)调用,但仍节省大约一半的调用。
\ $ \ endgroup \ $
–马丁·邦纳(Martin Bonner)支持莫妮卡(Monica)
16-10-26在10:38

#3 楼

我无法评论vnp的解决方案,但vnp第一次是对的:您可以在\ $ O(n ^ 2 \ log(n))\ $时间和\ $ O(n)\ $中强行使用它空间。您不需要\ $ O(n ^ 2)\ $空间,因为您不必存储\ $ a ^ 4 + b ^ 4 \ $或\ $ d ^ 4-c ^ 4 \的整个列表$预先付款。相反,您只需要能够按升序列出\ $ a ^ 4 + b ^ 4 \ $和\ $ d ^ 4-c ^ 4 \ $的值,然后将这两个值匹配即可。

要列出\ $ a ^ 4 + b ^ 4 \ $的值,您可以为每个\ $ a \ $保持\ $ b \ $的最小值,将其命名为\ $ b_a \ $ ,这样\ $ a ^ 4 + b_a ^ 4 \ ge k \ $,其中\ $ k \ $是您读取的\ $ a ^ 4 + b ^ 4 \ $的先前值。要读取下一个值,您需要找到\ $ a \ $的值,以使\ $ a ^ 4 + b_a ^ 4 \ $最小,您可以在\ $ O(\ log(n)中执行此操作)\ $使用堆或其他东西的时间。然后增加\ $ b_a \ $,就可以读取下一个值\ $ a ^ 4 + b ^ 4 \ $。

这是一个C ++ 11程序示例(使用一个GNU扩展以获取128位整数),可以在我的PC上约7小时内找到解决方案。 (这只是为了说明该方法-在其他方式上不是很有效。)

#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <queue>
#include <vector>
using std::priority_queue;
using std::vector;

//typedef long long int bigint;
typedef __int128 bigint;

// Look for a^4+b^4+c^4 = d^4 by comparing
// ascending list of a^4+b^4 with ascending list of d^4-c^4

vector<bigint> list;
vector<int> diffptr,sumptr;

bool sumcmp(int a0,int a1){
  int b0=sumptr[a0];
  int b1=sumptr[a1];
  return list[a0]+list[b0]>list[a1]+list[b1];
}

bool diffcmp(int c0,int c1){
  int d0=diffptr[c0];
  int d1=diffptr[c1];
  return list[d0]-list[c0]>list[d1]-list[c1];
}

int main(int ac,char**av){
  int a,c,i,n;

  n=500000;
  printf("Using n = %d\n",n);
  if(4*log(n)/log(2)+2>sizeof(bigint)*8){fprintf(stderr,"Error: %lu-bit type not large enough\n",sizeof(bigint)*8);exit(1);}

  bigint sumval,diffval;

  list.resize(n);
  diffptr.resize(n);
  sumptr.resize(n);

  priority_queue<int,vector<int>,decltype(&sumcmp)> sumnext(sumcmp);
  priority_queue<int,vector<int>,decltype(&diffcmp)> diffnext(diffcmp);

  for(i=0;i<n;i++)list[i]=bigint(i)*i*i*i;
  for(a=1;a<n;a++){sumptr[a]=a;sumnext.push(a);}
  for(c=0;c<n-1;c++){diffptr[c]=c+1;diffnext.push(c);}

  while(!sumnext.empty()&&!diffnext.empty()){
    a=sumnext.top();sumval=list[sumptr[a]]+list[a];
    c=diffnext.top();diffval=list[diffptr[c]]-list[c];
    if(sumval==diffval){printf("BINGO %d^4+%d^4+%d^4=%d^4\n",a,sumptr[a],c,diffptr[c]);fflush(stdout);}
    if(sumval<=diffval){
      sumnext.pop();
      sumptr[a]++;if(sumptr[a]<n)sumnext.push(a);
    }else{
      diffnext.pop();
      diffptr[c]++;if(diffptr[c]<n)diffnext.push(c);
    }
  }

}


评论


\ $ \ begingroup \ $
您能给我解释一下auto sumcmp = [&list,&sumptr](int a,int b){返回列表[sumptr [a]] + list [a]> list [sumptr [b]] +的语法清单[b]; };。那是函数定义还是您要定义一个新的向量?
\ $ \ endgroup \ $
–́Aidenhjj
16-10-27在9:29



\ $ \ begingroup \ $
我想您正在定义一个新的向量,在其中您遍历var a中的list并同时遍历var b中的sumptr。但是后来我有点迷路了。在这里,a和b的含义与我的问题相同吗?因为在这里对待它们的方式,从我有限的理解看来,a确实是a ^ 4,b是b。
\ $ \ endgroup \ $
–́Aidenhjj
16-10-27在9:44

\ $ \ begingroup \ $
可以使命名约定更加明确吗?
\ $ \ endgroup \ $
–́Aidenhjj
16-10-27在9:45

\ $ \ begingroup \ $
Aidenhjj:我已经更改了变量名称以匹配原始问题描述。 auto sumcmp =(etc)是所谓的lambda函数定义,其行为类似于函数,但可以引用变量list和sumptr。抱歉,这很可能分散了主要目的。可以避免使用全局变量和函数,或者使用略有不同的数据结构。
\ $ \ endgroup \ $
– Alex Selby
16-10-27在13:33

\ $ \ begingroup \ $
@AlexSelby“您需要找到\ $ a \ $的值,以使\ $ a ^ 4 + b_a ^ 4 \ $最小”,我不明白您的意思。难道不是总是\ $ a = 1 \ $吗?
\ $ \ endgroup \ $
–塔维安·巴恩斯(Tavian Barnes)
16-10-27在15:48



#4 楼

给出的所有答案(首次写此“答案”时)都忽略了一个重要的约束:本机类型。在C语言中,long int是32位(或更大)的带符号类型,表示可以(算作)表示的最大正值是\ $ 2 ^ {31} -1 \ $。函数计算四次幂并[[可以算到]]返回正确的long int的最大可能输入为\ $ \ left \ lfloor \ sqrt [4] {2 ^ {31} -1} \ right \ rfloor = 215 \ $。即使您移至unsigned long long int(64位(或更高版本)的无符号类型),\ $ \ left \ lfloor \ sqrt [4] {2 ^ {64} -1} \ right \ rfloor = 65 \,535 \ $。浮点类型将使您无所适从:由于需要精度,仅尾数位才有价值。 (如果您可以访问128位类型,则只需\ $ \ left \ lfloor \ sqrt [4] {2 ^ {128} -1} \ right \ rfloor = 4 \,294 \,967 \ ,295 \ $。)

如果本机类型不能完全满足要求,则需要一个“大数字库”来处理此类值。一个警告:要调出一个库很可能会导致性能降低。以仅与低位进行加和比较的方式,但是您必须非常小心,并且可能会出现假阳性。此时,您可以调用“大数库”以验证查找结果。

评论


\ $ \ begingroup \ $
如果您在64位系统上运行,则GCC支持__int128类型,该类型可以处理所需的数字。
\ $ \ endgroup \ $
–马克
16-10-25在21:27

\ $ \ begingroup \ $
您不需要任意精度,只需双字数学,这远远没有那么慢。只保留低位作为快速过滤器是一个好主意,但是它破坏了单调性,因此二进制搜索方法将不再起作用,其他任何sqrt功能也将不再起作用。但是它应该很好地应用于@vnp基于筛子的搜索。
\ $ \ endgroup \ $
– Ben Voigt
16-10-25在22:13

\ $ \ begingroup \ $
C中的long int至少具有32位。
\ $ \ endgroup \ $
– 5gon12eder
16-10-26在0:14

#5 楼

没有人提到过一些有趣的事情,但这在寻找最小的解决方案时是一种改进:

如果\ $ \ gcd(a,b,c)\ neq 1 \ $那么\ $ a ^ 4 \ $,\ $ b ^ 4 \ $,\ $ c ^ 4 \ $和\ $ d ^ 4 \ $都可以被该gcd整除为4的幂,从而给出较小的解决方案。

因此,\ $ a \ $,\ $ b \ $和\ $ c \ $中的至少一个必须为奇数,因为即使它们全部都是偶数,它们也有共同的2除数,即\ \ gcd(a,b,c)\ geq 2 \ $。通过检查它们是否全部为偶数,在执行d的循环之前,您将大幅度削减可能的搜索集。

如果\ $ d \ $是偶数,则沿着相同的几行,则2必须除以\ $ a ^ 4 + b ^ 4 + c ^ 4 \ $,但是我们已经提到,它们甚至不可能成为最小的解决方案。如果三个变量中的两个(或零个)是偶数,则它们的四次幂之和将是奇数(偶数+偶数+奇数=奇数;奇数+奇数+奇数=奇数),因此,如果d为偶数,则1数字必须为偶数,其他2为奇数。

如果\ $ d \ $为奇数,则2无法将\ $ a ^ 4 + b ^ 4 + c ^ 4 \ $相除。如果只有一个数字是偶数,那么它们的四次幂之和将是偶数(偶数+奇数+奇数=偶数)。这样,它们要么都是奇数,要么只有1是奇数。

使用此函数,您可以基于\ $ a,b,c \ $的集合来确定D是奇数还是偶数,从而减少解决方案通过相应地剪裁最里面的循环将其分成两半。结合增加的值d> c> b> a,这将大大改善您的处理。

评论


\ $ \ begingroup \ $
实际上,它甚至比这更好:所有的偶数取四次幂时都可被16整除,而所有奇数取四次幂时除以16所得的余数均为1。因此,它必须是-因为a,b,c和d的全部都等于一个小解,所以d必须是奇数,而a,b和c之一也必须是奇数,因为那是在两侧获得相同的余数mod 16的唯一方法。
\ $ \ endgroup \ $
–丹·乌兹南斯基(Dan Uznanski)
16-10-26在1:42



\ $ \ begingroup \ $
因此,使用@DanUznanski简化,如果a为奇数,则b和c都必须为偶数,如果a为偶数,则b可以为任一,并且c必须与b相反。切成一半以上。我们可以对mod 3进行类似的技巧吗?
\ $ \ endgroup \ $
–马丁·邦纳(Martin Bonner)支持莫妮卡(Monica)
16-10-26在10:52

\ $ \ begingroup \ $
“既然所有的gcd都是2,那是因为” -2不是gcd。
\ $ \ endgroup \ $
– JimmyB
16-10-27在14:02

\ $ \ begingroup \ $
@JimmyB是的,不一定。我将编辑说除数。
\ $ \ endgroup \ $
– Mascoj
16-10-27在14:56

\ $ \ begingroup \ $
@MartinBonner-取模3表示a,b和c都不可以是3的倍数,或者a,b和c恰好两个必须是3的倍数。模5有点更有用:a,b和c中的两个必须是5的倍数。
\ $ \ endgroup \ $
–David Hammen
16-10-31在15:55

#6 楼

阅读源代码

既然您已经确定了作者,为什么不阅读原始论文呢?

有些误解

埃尔基斯发现的不是问题中引用的那些。罗杰·弗莱(Roger Frye)使用计算机发现了它们。他在1988年是如何做到的?在论文中提到:


后记。

我们的第一个反例
$$(A,B,C; D)=(2682440 ,15365639,18796760; 20615673)$$
似乎仍然超出了合理的详尽计算机搜索的范围,仍然有可能通过这种搜索找到较小的解决方案。在听到第一个解决方案后不久,Thinking Machines Corporation的Roger Frye询问是否最小。我不知道,但建议人们如何穷举地寻找较小的解决方案:消除共同因素,并在必要时置换\ $ A \ $,\ $ B \ $,\ $ C \ $,我们可能会认为并且不能被5整除,并且\ $ C $$ 95800 ^ 4 + 217519 ^ 4 + 414560 ^ 4 = 422481 ^ 4。$$
他继续搜索,发现该解决方案在\ $ D <10 ^ 6 \ $范围内是唯一的。该解决方案出现在参数化(6)上,其中\ $(m,n)=(20,-9)\ $。我们将Frye的结果包括在他的允许下。


资料来源:Elkies,NoamD。数学计算(1988):825-835。对于不熟悉已故的Think Machines Corporation的人来说,这是1980年代和1990年代生产大型并行计算机的公司。

综上所述,这些提示表明,使用穷举搜索和这些数字理论测试的多线程方法绝对应该在小时数而不是数年的范围内适用于现代机器。

评论


\ $ \ begingroup \ $
关于弗莱(Frye)的好处是,但是您不需要任何花哨的东西就可以在一台现代机器上将其花费数小时:简单的蛮力就可以在数小时内得出答案。如果您通过模数限制,手工编码(而不是库)数据结构,多线程等来努力使其快速运行,那么它可能会在几分钟或更短的时间内运行。
\ $ \ endgroup \ $
– Alex Selby
16-10-30在11:01

\ $ \ begingroup \ $
@Edward:我发布了一个示例程序(未优化),其答案在几个小时内就找到了解决方案。
\ $ \ endgroup \ $
– Alex Selby
16-10-31在23:10

\ $ \ begingroup \ $
我发布了另一种经过优化的方法,该方法在大约40秒内找到了解决方案。
\ $ \ endgroup \ $
– Alex Selby
16年11月2日,1:11

#7 楼

我建议重写一个问题,找到\ $ a,b,c,d \ $,使\ $ a ^ 4 + b ^ 4 = d ^ 4-c ^ 4 \ $。现在,您只能成对使用电源。建立总和表需要\ $ O(n ^ 2)\ $。建立差异表也需要\ $ O(n ^ 2)\ $。对它们进行排序需要\ $ O(n ^ 2 \ log {n})\ $。匹配排序的表在类似线性合并的扫描中需要\ $ O(n ^ 2)\ $。

总体复杂度显然是\ $ O(n ^ 2 \ log {n})\ $。

评论


\ $ \ begingroup \ $
建立总和表需要\ $ O(n ^ 2)\ $时间和空间,这可能会限制其适用性。
\ $ \ endgroup \ $
– Ben Voigt
16-10-25在20:25



\ $ \ begingroup \ $
@BenVoigt正确;但是有一种方法可以使用\ $ O(n)\ $空间和\ $ O(n ^ 2)\ $时间。我故意没进去。
\ $ \ endgroup \ $
–vnp
16-10-25在21:04

\ $ \ begingroup \ $
@ JS1事后我很好奇自己。在一天结束时给我,要么我发布代码,要么承认自己错了。
\ $ \ endgroup \ $
–vnp
16-10-25在21:48

\ $ \ begingroup \ $
@ JS1天结束了。我没有提供代码,所以我必须承认自己错了。
\ $ \ endgroup \ $
–vnp
16-10-26在7:43

\ $ \ begingroup \ $
@ gnasher729:但是没有人关心n ^ 4运算,因为有几个答案显示了O(n ^ 3)时间和O(1)空间中的算法。
\ $ \ endgroup \ $
– Ben Voigt
16-10-27在19:04

#8 楼

这与我之前提交的其他(优先级队列)答案不同。它更简单,并且可以以更好的恒定因子实现\ $ O(n ^ 2 \ log(n))\ $时间和\ $ O(n)\ $内存。您选择一些间隔\ $ [A_i,A_ {i + 1})\ $覆盖\ $ [0,n ^ 4)\ $,然后分别(对于每个\ $ i \ $)分别找到\ $ a,b \ $使得\ $ a ^ 4 + b ^ 4 \ in [A_i,A_ {i + 1})\ $和\ $ c,d \ $使得\ $ d ^ 4-c ^ 4 \ in [A_i,A_ {i + 1})\ $。然后,对\ $ a ^ 4 + b ^ 4 \ $和\ $ d ^ 4-c ^ 4 \ $的两个列表进行排序,并检查它们的交集。

以确保它按正确的时间顺序运行,您需要确保\ $ a,b \ $与\ $ a ^ 4 + b ^ 4 \ in [A_i,A_ {i + 1})\ $的数量成比例到\ $ n \ $,并且大致独立于\ $ i \ $。您可以通过使用\ $ A_i = C \ cdot n ^ 2 \ cdot i ^ 2 \ $来实现某些常量\ $ C \ $。 \ $ C \ $的最佳值取决于您的特定硬件特性。

(如果执行此操作(下面的第一个程序),则运行时间可以找到上述解决方案\ $ 95800 ^ 4 + 414560 ^ 4 + 217519 ^ 4 = 422481 ^ 4 \ $似乎要花一个多小时(在我的台式机上)。这使用__int128,它是一个GCC扩展,提供128位整数。

为了娱乐,我还对其进行了修改,以使用最简单(也是最有用)的模关系:\ $ a,b \ $是\ $ 5 \ $和\ $ d ^ 4的倍数-c ^ 4 \ $是\ $ 625 \ $的倍数。这是下面的第二个程序,它将在大约四分钟内找到解决方案。

我还对其进行了修改,使其成为多线程的。这很简单,因为可以单独处理块\ $ [A_i,A_ {i + 1})\ $。这是下面的第三个程序,它大约在32秒内找到了解决方案(在我的十六进制核心计算机上)。

另一种加快处理速度的技术是在对两个列表的元素进行排序和比较之前,以\ $ 2 ^ {64} \ $为模减少其元素。如果\ $ a ^ 4 + b ^ 4 \ $与\ $ d ^ 4-c ^ 4 \ $ mod \ $ 2 ^ {64} \ $之间存在匹配项,则需要额外检查其是否为真,这意味着它来自未减少的比赛。 (它很可能是正版的,因此额外的检查所花费的时间可以忽略不计。)由于大多数时间都花在了排序上,所以对64位的数量比对128位的数量进行排序更好。这将内存需求减少了将近2倍,运行时间减少了约30%,与十六进制核心计算机上的第一个解决方案相比,节省了约23秒的时间。这是下面的第四个程序。

(它确实做出了像将128位数字强制转换为64位的假设与采用最低64位相同,而long long int是64位的假设,留下了一些Unix专用的计时代码,所以它不是那么漂亮或可移植。改进。
如果使用n=3000000运行程序4,它将在大约5分钟后(在十六进制核心计算机上)找到新的解决方案\ $ 673865 ^ 4 + 1390400 ^ 4 + 2767624 ^ 4 = 2813001 ^ 4 \ $。如果使用n=9000000MAXMEM=12e9运行它,则大约80分钟后,它会发现\ $ 1705575 ^ 4 + 5507880 ^ 4 + 8332208 ^ 4 = 8707481 ^ 4 \ $。
可以增强此间隔方法的运行能力通过使用连续分数,\ $ O(n ^ 2)\ $时间和\ $ O(n ^ {2/3})\ $内存。假设这大约是32GB的内存,这很奇怪(这意味着令人讨厌的常量因子开销),可能仅对\ $ n \ ge10 ^ 8 \ $有用。由Robert Gerbicz设计的,这里和这里都记录了(俄语)。他们的程序使用更多的“模滤波器”,并使用中文余数定理,而不是中间相遇,这意味着它使用更少的内存,而且速度更快。他们找到了\\ dd = 1871713857 \ $为止的解决方案。

程序1-基本块化

// Look for a^4+b^4+c^4 = d^4 by comparing the sorted list of a^4+b^4 in the range [A,B)
// with the sorted list of d^4-c^4 in the range [A,B). Use a suitable collection of
// [A_i,B_i) covering the range [0,n^4).

#include <cstdio>
#include <cmath>
#include <cassert>
#include <algorithm>
#include <vector>
using std::vector;
using std::min;
using std::max;

//typedef long long int bigint;
typedef __int128 bigint;

vector<bigint> pow4;
const double MAXMEM=4e9; // Allow the use of up to (approx) this memory in bytes
int gcd(int x,int y){if(y==0)return x; return gcd(y,x%y);}

void printdecode(int n,bigint cv){// Find a,b,c,d given the common value, cv=a^4+b^4=d^4-c^4
  int a,b,c,d;
  for(a=1;a<n;a++){
    bigint b4=cv-pow4[a];
    if(pow4[a]<=b4){
      b=int(round(sqrt(sqrt(b4))));
      if(pow4[b]==b4)break;
    }
  }
  assert(a<n);
  for(c=0;c<n-1;c++){
    d=int(round(sqrt(sqrt(cv+pow4[c]))));
    if(pow4[d]-pow4[c]==cv)break;
  }
  assert(c<n-1);
  if(gcd(a,gcd(b,gcd(c,d)))==1)printf("BINGO"); else printf("MULTIPLE");
  printf(" %d^4+%d^4+%d^4=%d^4\n",a,b,c,d);fflush(stdout);
}

int main(int ac,char**av){
  int a,b,c,d,i,j,n,s0,s1;
  bigint chunk,chunksize;
  vector<bigint> list0,list1;

  n=500000;
  printf("Using n = %d\n",n);
  if(4*log(n)/log(2)+2>sizeof(bigint)*8){fprintf(stderr,"Error: %lu-bit type not large enough\n",sizeof(bigint)*8);exit(1);}

  pow4.resize(n);
  for(i=0;i<n;i++)pow4[i]=bigint(i)*i*i*i;

  chunk=0;chunksize=bigint(min(5.*n,MAXMEM/26.));
  printf("Using chunksize = %lld\n",(long long int)(chunksize));

  for(chunk=0;chunk<bigint(n)*n;chunk+=chunksize){
    // We will try a^4+b^4 in the range [chunk^2,(chunk+chunksize)^2)

    bigint lb=chunk*chunk;
    bigint ub=min(chunk+chunksize,bigint(n)*n);ub*=ub;

    list0.resize(0);
    for(a=1;a<n&&pow4[a]<ub;a++){
      int bmin=max(int(sqrt(sqrt(max(lb-pow4[a],bigint(0))))),a);
      int bmax=min(int(sqrt(sqrt(ub-pow4[a]))),n);
      for(b=bmin;b<bmax;b++)list0.push_back(pow4[a]+pow4[b]);
    }
    std::sort(list0.begin(),list0.end());

    list1.resize(0);
    for(c=0;c<n-1;c++){
      int dmin=min(int(sqrt(sqrt(lb+pow4[c]))),n);
      int dmax=min(int(sqrt(sqrt(ub+pow4[c]))),n);
      for(d=dmin;d<dmax;d++)list1.push_back(pow4[d]-pow4[c]);
    }
    std::sort(list1.begin(),list1.end());

    i=j=0;
    s0=list0.size();s1=list1.size();
    while(i<s0&&j<s1){
      if(list0[i]==list1[j]){
        printdecode(n,list0[i]);
        j++;continue;
      }
      if(list0[i]<list1[j])i++; else j++;
    }
  }
}


程序2-与程序1一样,但也使用模5减法运算/>程序3 –作为程序2,但也使用多线程处理

// Look for a^4+b^4+c^4 = d^4 by comparing the sorted list of a^4+b^4 in the range [A,B)
// with the sorted list of d^4-c^4 in the range [A,B). Use a suitable collection of
// [A_i,B_i) covering the range [0,n^4). Use a=b=0 mod 5, d^4==c^4 mod 625.

#include <cstdio>
#include <cmath>
#include <cassert>
#include <algorithm>
#include <vector>
using std::vector;
using std::min;
using std::max;

//typedef long long int bigint;
typedef __int128 bigint;

#define MAXMEM 4e9 // Allow the use of up to (approx) this memory in bytes

vector<bigint> pow4;

int gcd(int x,int y){if(y==0)return x; return gcd(y,x%y);}

void printdecode(int n,bigint cv){// Find a,b,c,d given the common value, cv=a^4+b^4=d^4-c^4
  int a,b,c,d;
  for(a=1;a<n;a++){
    bigint b4=cv-pow4[a];
    if(pow4[a]<=b4){
      b=int(round(sqrt(sqrt(b4))));
      if(pow4[b]==b4)break;
    }
  }
  assert(a<n);
  for(c=0;c<n-1;c++){
    d=int(round(sqrt(sqrt(cv+pow4[c]))));
    if(pow4[d]-pow4[c]==cv)break;
  }
  assert(c<n-1);
  if(gcd(a,gcd(b,gcd(c,d)))==1)printf("BINGO"); else printf("MULTIPLE");
  printf(" %d^4+%d^4+%d^4=%d^4\n",a,b,c,d);fflush(stdout);
}

int main(int ac,char**av){
  int a,b,c,d,i,j,n,r,s0,s1;

  n=500000;
  printf("Using n = %d\n",n);
  if(4*log(n)/log(2)+2>sizeof(bigint)*8){fprintf(stderr,"Error: %lu-bit type not large enough\n",sizeof(bigint)*8);exit(1);}

  bigint chunk,chunksize;
  vector<bigint> list0,list1;

  pow4.resize(n);

  for(i=0;i<n;i++)pow4[i]=bigint(i)*i*i*i;

  chunk=0;chunksize=min(bigint(300)*n,bigint((MAXMEM-2*n*sizeof(int))*2.8));
  printf("Using chunksize = %lld\n",(long long int)(chunksize));
  for(chunk=0;chunk<bigint(n)*n;chunk+=chunksize){

    // We will try a^4+b^4 in the range [chunk^2,(chunk+chunksize)^2)
    bigint lb=chunk*chunk;
    bigint ub=min(chunk+chunksize,bigint(n)*n);ub*=ub;

    list0.resize(0);
    for(a=5;a<n&&pow4[a]<ub;a+=5){
      int bmin=max(int(sqrt(sqrt(max(lb-pow4[a],bigint(0))))),a);
      int bmax=int(sqrt(sqrt(ub-pow4[a])));
      r=(-bmin)%5;if(r<0)r+=5;
      for(b=bmin+r;b<bmax;b+=5)if(!(a&b&1))list0.push_back(pow4[a]+pow4[b]);
    }
    std::sort(list0.begin(),list0.end());

    list1.resize(0);
    int rou[4]={1,182,624,443};// Fourth roots of unity mod 625
    for(c=1;c<n-1;c++)if(c%5){
      int dmin=min(int(sqrt(sqrt(lb+pow4[c]))),n);
      int dmax=min(int(sqrt(sqrt(ub+pow4[c]))),n);
      for(i=0;i<4;i++){
        // Need to find d in [dmin,dmax) congruent to rou[i]*c mod 625
        r=(rou[i]*(c%625)-dmin)%625;
        if(r<0)r+=625;
        for(d=dmin+r;d<dmax;d+=625)list1.push_back(pow4[d]-pow4[c]);
      }
    }
    std::sort(list1.begin(),list1.end());

    //printf("%10d   %10lu   %10lu\n",int(chunk/chunksize),list0.size(),list1.size());
    i=j=0;
    s0=list0.size();s1=list1.size();
    while(i<s0&&j<s1){
      if(list0[i]==list1[j]){
        printdecode(n,list0[i]);
        j++;continue;
      }
      if(list0[i]<list1[j])i++; else j++;
    }

  }
}


程序4 –作为程序3,但将128位整数减少为64位

// Look for a^4+b^4+c^4 = d^4 by comparing the sorted list of a^4+b^4 in the range [A,B)
// with the sorted list of d^4-c^4 in the range [A,B). Use a suitable collection of
// [A_i,B_i) covering the range [0,n^4). Use a=b=0 mod 5, d^4==c^4 mod 625.
// Parallelise over [A_i,B_i).

// Compile with -pthread flag

#include <cstdio>
#include <cmath>
#include <cassert>
#include <algorithm>
#include <vector>
#include <thread>
#include <mutex>
using std::vector;
using std::min;
using std::max;

//typedef long long int bigint;
typedef __int128 bigint;

int n;// Maximum value of d
const double MAXMEM=4e9; // Allow the use of up to (approx) this memory in bytes
const int nthreads=12;
bigint chunk,chunksize;
vector<bigint> pow4;
std::mutex chunkserver,printer;

int gcd(int x,int y){if(y==0)return x; return gcd(y,x%y);}

void printdecode(bigint cv){// Find a,b,c,d given the common value, cv=a^4+b^4=d^4-c^4
  int a,b,c,d;
  for(a=1;a<n;a++){
    bigint b4=cv-pow4[a];
    if(pow4[a]<=b4){
      b=int(round(sqrt(sqrt(b4))));
      if(pow4[b]==b4)break;
    }
  }
  assert(a<n);
  for(c=0;c<n-1;c++){
    d=int(round(sqrt(sqrt(cv+pow4[c]))));
    if(pow4[d]-pow4[c]==cv)break;
  }
  assert(c<n-1);
  std::lock_guard<std::mutex> lock(printer);
  if(gcd(a,gcd(b,gcd(c,d)))==1)printf("BINGO"); else printf("MULTIPLE");
  printf(" %d^4+%d^4+%d^4=%d^4\n",a,b,c,d);fflush(stdout);
}

bigint nextchunk(){// Returns next chunk, or -1 if none left
  std::lock_guard<std::mutex> lock(chunkserver);
  if(chunk>=bigint(n)*n)return -1;
  bigint chunkcopy=chunk;
  chunk+=chunksize;
  return chunkcopy;
}

void searcher(){
  int a,b,c,d,i,j,r,s0,s1;
  bigint ch;
  vector<bigint> list0,list1;
  while((ch=nextchunk())>=0){

    // We will try a^4+b^4 in the range [chunk^2,(chunk+chunksize)^2)
    bigint lb=ch*ch;
    bigint ub=min(ch+chunksize,bigint(n)*n);ub*=ub;

    list0.resize(0);
    for(a=5;a<n&&pow4[a]<ub;a+=5){
      int bmin=max(int(sqrt(sqrt(max(lb-pow4[a],bigint(0))))),a);
      int bmax=int(sqrt(sqrt(ub-pow4[a])));
      r=(-bmin)%5;if(r<0)r+=5;
      for(b=bmin+r;b<bmax;b+=5)if(!(a&b&1))list0.push_back(pow4[a]+pow4[b]);
    }
    std::sort(list0.begin(),list0.end());

    list1.resize(0);
    int rou[4]={1,182,624,443};// Fourth roots of unity mod 625
    for(c=1;c<n-1;c++)if(c%5){
      int dmin=min(int(sqrt(sqrt(lb+pow4[c]))),n);
      int dmax=min(int(sqrt(sqrt(ub+pow4[c]))),n);
      for(i=0;i<4;i++){
        // Need to find d in [dmin,dmax) congruent to rou[i]*c mod 625
        r=(rou[i]*(c%625)-dmin)%625;
        if(r<0)r+=625;
        for(d=dmin+r;d<dmax;d+=625)list1.push_back(pow4[d]-pow4[c]);
      }
    }
    std::sort(list1.begin(),list1.end());

    //printf("%10d   %10lu   %10lu\n",int(ch/chunksize),list0.size(),list1.size());
    i=j=0;
    s0=list0.size();s1=list1.size();
    while(i<s0&&j<s1){
      if(list0[i]==list1[j]){
        printdecode(list0[i]);
        j++;continue;
      }
      if(list0[i]<list1[j])i++; else j++;
    }

  }
}

int main(int ac,char**av){
  int i,t;

  n=500000;
  printf("Using n = %d\n",n);
  if(4*log(n)/log(2)+2>sizeof(bigint)*8){fprintf(stderr,"Error: %lu-bit type not large enough\n",sizeof(bigint)*8);exit(1);}

  pow4.resize(n);
  for(i=0;i<n;i++)pow4[i]=bigint(i)*i*i*i;

  chunk=0;chunksize=min(bigint(300)*n,bigint(MAXMEM*2.8/nthreads));
  printf("Using chunksize = %lld\n",(long long int)(chunksize));

  vector<std::thread> thr(nthreads);
  for(t=0;t<nthreads;t++)thr[t]=std::thread(searcher);
  for(t=0;t<nthreads;t++)thr[t].join();
}


评论


\ $ \ begingroup \ $
讨厌格式化,但是代码很可靠。注意a和b不能同时为奇数,可以节省更多时间。如果在list0.push_back(...)之前添加另一个检查,例如:if(!(a&1&b)),则线程版本节省了我机器上约25%的时间(69秒vs. 90(原始)。
\ $ \ endgroup \ $
–爱德华
16年2月2日,下午2:55

\ $ \ begingroup \ $
好点。我不会涉及其他模态改进,因为它们变得相当复杂,但是您的改进很简单,当然值得拥有。我已经编辑了上面的代码以合并更改。 (对格式很抱歉-这是我所习惯的。)
\ $ \ endgroup \ $
– Alex Selby
16-11-2在9:43



\ $ \ begingroup \ $
我考虑过接受这个答案,但是Zeta的答案非常完整,对我有很多帮助。当我充分理解您的答案后,我会回到他们那里。
\ $ \ endgroup \ $
–́Aidenhjj
16年2月2日,12:39

\ $ \ begingroup \ $
我想这是我的小玩笑使它受欢迎!否则,这是一个相当标准的“我对编程一无所知”的问题。
\ $ \ endgroup \ $
–́Aidenhjj
16年2月2日,12:59

\ $ \ begingroup \ $
@Aidenhjj没问题-我不担心这个答案是否被接受。我认为将您的问题解释为“解释哪种编程技术可以改善此特定方法?”,Zeta所做的解释,或者“如果您可以稍微改变一下算法,您将如何做得更好?”是合理的。我选择这样做。您的问题提出了很多经常发生的有用技术。中间相遇技术适用于许多搜索情况,将\ $ N \ $的搜索量减少为\ $ \ sqrt {N} \ $。合并到独立的集合中通常用于减少内存需求。等等。
\ $ \ endgroup \ $
– Alex Selby
16年2月2日在18:09

#9 楼

不需要乘法。

考虑如何计算一系列平方1,4,9,16

unsigned sq = 1;
for (unsigned x=1;; x++) {
  sq += x + x + 1;
  printf("%u\n", sq);
}


考虑如何计算平方系列的1,8,27,64多维数据集

unsigned sq = 1;
unsigned cube = 1;
for (unsigned x=1;; x++) {
  cube += (sq + sq + sq) + (x + x + x) + 1;
  sq += x + x + 1;
  printf("%u\n", cube);
}


四次方的相似操作。

需要任意宽的数学运算才能达到a = 95800; b = 217519; c = 414560; d = 422481,需要80位数学运算。建议使用支持uint128_t或同等功能的编译器。只需整数加和比较就可以了。

我将递增计算a**4 + b**4 + c**4d**4,将其中的较小者递增二。当然,当它们相等时-瞧!


示例代码。使用factor = 1作为最终代码。使用factor >= 7进行测试。

使用factor允许代码仅运行测试所需值的一部分。 factor为50需要几分钟。 factor为20花费了4个小时。预期1的factor将需要数年(5?)。当使用factor < 7时,您需要的确是128位的uint128t类型,这与这里使用的uintmax_t占位符不同。

#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>

// Adjust to some 128 bit type
// The below is a place-holder for a true 128-bit type
typedef uintmax_t uint_fast128t;

typedef struct {
  uint_fast128t x4;  // 4th power of the value
  uint_fast128t x3;  // cube of the value
  uint_fast64_t x2;  // square of the value
  uint_fast32_t x1;  // location of OP's value a,b,c or d
                     // Assume a,b,c,d are 20 bits or less.
}quad;

#define quad_next(x) { \
  x.x4 += (uint_fast128t) x.x3*4 + x.x2*6 + 4*x.x1 + 1; \
  x.x3 += (uint_fast128t) x.x2*3 + x.x1*3 + 1; \
  x.x2 += (uint_fast64_t) x.x1*2 + 1; \
  x.x1++; \
  }

#define factor 20
uint_fast32_t a_max = 95800 / factor;
uint_fast32_t b_max = 217519 / factor;
uint_fast32_t c_max = 414560 / factor;
uint_fast32_t d_max = 422481 / factor;

void Elkies_B(quad a, quad b) {
  quad c = b;
  quad d = c;
  c.x4 += b.x4;

  // Could use a more efficient step here
  while (d.x4 < c.x4) {
    quad_next(d);
  }
  while (c.x1 <= c_max) {
    while (d.x4 < c.x4) {
      quad_next(d);
    }
    if (d.x4 == c.x4) {
      // Success! Print a,b,c,d
      printf("+ a:%lu b:%lu c:%lu d:%lu\n", //
              (unsigned long) a.x1, (unsigned long) b.x1, //
              (unsigned long) c.x1, (unsigned long) d.x1);
      exit(0);
    }
    quad_next(c);
  }
}

void Elkies_A(quad a) {
  quad b = a;
  b.x4 += a.x4;
  while (b.x1 <= b_max) {
    Elkies_B(a, b);
    quad_next(b);
  }
}

void Elkies(void) {
  quad a = { 1, 1, 1, 1 };
  while (a.x1 <= a_max) {
    printf("- a:%lu %llu %ju %ju\n", //
            (unsigned long) a.x1, (unsigned long long) a.x2, //
            a.x3, a.x4);
    fflush( stdout);
    Elkies_A(a);
    quad_next(a);
  }
  printf("Failed\n");
}

int main() {
  Elkies();
}


评论


\ $ \ begingroup \ $
预计运行时间:5年,
\ $ \ endgroup \ $
–chux-恢复莫妮卡
16-10-26在2:42

\ $ \ begingroup \ $
尚不清楚代码实际上在数学上做什么。你能解释一下吗?
\ $ \ endgroup \ $
–爱德华
16-10-26在3:17

\ $ \ begingroup \ $
@Edward哪些代码部分不清楚?
\ $ \ endgroup \ $
–chux-恢复莫妮卡
16-10-26在15:20

\ $ \ begingroup \ $
在我所知道的任何平台上,uintmax_t都不是128位,当然也不是带有gcc的x86_64 Linux。您将需要使用__uint128_t。
\ $ \ endgroup \ $
–塔维安·巴恩斯(Tavian Barnes)
16-10-26在15:26

\ $ \ begingroup \ $
@TavianBarnes //调整为128位类型typedef uintmax_t uint_fast128t;它只是编译器可以访问的任何128位类型的占位符-根据需要进行调整。 __uint128_t不是标准C的一部分,但是有效的解决方案将使用某些扩展名,例如__uint128_t。 IAC,直到因子<7才需要大于64位数学运算。
\ $ \ endgroup \ $
–chux-恢复莫妮卡
16-10-26在15:30



#10 楼

我不确定它有多少实际用途,但是除了d以及\ $ a \ $,\ $ b \ $和\ $ c \ $之一之外,我们必须对5和10做模属性。


模5,四次幂是0或1。将三个这三个数之和合成第四的唯一方法是在LHS上将三个三分之二设为0和。其中\ $ d ^ 4 \ $和\ $ a ^ 4 \ $,\ $ b ^ 4 \ $和\ $ c ^ 4 \ $中的一个都是1模5(即d以1、3、7或
以10为底,第四次幂以0000,E1,U6或625结尾,其中E是偶数位,U是奇数位。考虑将这些方法组合起来的方法,并消除\ $ 0 + 0 + 5 = 5 \ $和\ $ 0 + 0 + 6 = 6 \ $的含义不是最低的,我们认为四次幂的结束位满足其中任一位\ $ 0 + 0 + 1 = 1 \ $的排列(如Noam Elkies的解决方案)或\ $ 0 + 5 + 6 = 1 \ $的某些排列。

因此必须有5的2的倍数在LHS上,它们中的至少一个必须是10的倍数(在Elkies的解决方案中,它们都是)。那也应该大大减少搜索。

在查看任何其他模量时我看不到优势。如果RHS只是一个完美的正方形(成为四次方的必要条件),并且只有在满足此测试的条件下,才测试它是否也是正方形的正方形。任何收益可能取决于您对\ $ d \ $和\ $ d ^ 4 \ $的测试方法是自上而下(根源能力)还是自下而上(根源其权力)。

#11 楼

您正在谈论的解决方案不是由Elkies找到的,而是由Fry找到的。在[Elkies]中对此进行了描述:


欧拉(Euler)于1769年推测出丢番图方程
\ $ A ^ 4 + B ^ 4 + C ^ 4 = D ^ 4 \ $,(...)没有正整数的解决方案。





(...)消除公共因素,并在必要时对\ $ A,B,C \ $进行排列,我们可能会考虑\ $ D \ $奇数且不可被\ $ 5 \ $和\ $ C \ lt D \ $整除,因此\ $ D ^ 4-C ^ 4 \ $被\ $ 625 \ $可整除,并且满足其他几个同余和可除属性,并为每个此类D和C寻找D ^ 4-C ^ 4表示为\ $ A ^ 4 + B ^ 4 \ $,其中\ $ A,B \ $可被\ $ 5 \ $整除。弗莱(Frye)将其翻译成计算机程序,并在[...]上运行了大约100个小时(在1988年),以找到欧拉猜想的最小反例。
他的算法
在这里:[Frye]。

[Ward]进行了第一次调查(值小于10000)。他没有使用电脑。该文件不是免费提供的。
但是可以在这里找到包含重要定理1和2的第一页,并且可以在此处找到从中引用的摘要。超过22万的文章发表在这里:[Lander,Parkin,Selfridge]

最近的研究:[Ekl]和[Bernstein]。

最高级的搜索(值较小2 000 000)我发现是由Robert Gerbicz的页面发布的:这是C程序euler413.c。

目前(2016-11-04),该文件错误地两次包含了该程序。删除从行1478到末尾的所有行。


@AlexSelby的注意事项:
要使代码在64位系统上工作,您需要更改5行multipliers17=(unsigned int**) (malloc) (17*17*sizeof(unsigned int));,依此类推,将sizeof(unsigned int)替换为sizeof(unsigned int*)(以及以下四行类似)



[Bernstein],Bernstein,D.J .; p(a)+ q(b)= r(c)+ s(d)的枚举解;计算数学,第70卷,N0 233,第389-394页

[Ekl],Ekl,R.L .;幂等式求和的新结果;计算数学,第67卷,第223号,1998年7月,第1309-1315页。[Elkies] Elkies,Noam D;在\ $ A ^ 4 + B ^ 4 + C ^ 4 = D ^ 4 \ $;十月51/184,《计算数学》。 1988,825-835

[Frye] Frye,Roger;在连接机上找到$ 95800 ^ 4 + 217519 ^ 4 + 414560 ^ 4 = 422481 ^ 4 $
[Lander,Parkin,Selfridge] Lander,L。 J. Parkin T.R .;塞尔弗里奇,J。相同权力的和等于调查;数学。 《计算论》,1967年第21卷,[Ward] Ward,摩根。欧拉问题的四分之三总和。数学公爵。 J.15(1948),没有。 3,827--837这是摘要,这是Wards的文章的第一页


[Robert Gerbicz'euler413.c] https://sites.google.com/site/ robertgerbicz / euler413.c

[Robert Gerbicz'euler413.exe] https://sites.google.com/site/robertgerbicz/euler413.exe



只是为了好玩!

也可以在Robert Gerbicz的页面euler413.exe上找到Windows的编译版本。我尝试了。我对该程序,其参数及其输出一无所知。我得到以下信息:

C:&gt euler413.exe
I haven't found unfinished work!
Please give R parameter, the Range will be R*10240000: 10
Do you want to start an exhaustive search in this Range or
only to search for special solutions?
( y=full search, n=special search ) n
Give the start value of a0. It should be 0&lt=start_a0&lt=16384: 0
Give the end value of a0. It should be start_a0&lt=end_a0&lt=16384: 16384
Building up some tables
Done
Testing: a0=1
Finished: a0=1,Range=102400000,type=0,Time: 0h0m0s,Date: Fri Nov 04 18:20:33 2016
Testing: a0=9
Finished: a0=9,Range=102400000,type=0,Time: 0h0m1s,Date: Fri Nov 04 18:20:34 2016
....


该程序在屏幕和以下文件中创建了大量输出:

euler413work.txt
stat_euler(4,3,1).txt
results_euler(4,1,3).txt


euler413work.txt是状态文件。如果使用停止该程序,然后再在同一目录中启动它,它将在停止该点时继续工作。 stat_euler(4,3,1).txt记录您还可以在屏幕上看到的定时输出。到目前为止建立的解决方案存储在results_euler(4,1,3).txt中。

大约9分钟后,结果文件包含以下几行:

Solution found! 98438073^4=50681927^4+96592480^4+22321400^4
Solution found! 17321721^4=8918279^4+16996960^4+3927800^4
Solution found! 40980657^4=21099343^4+40212320^4+9292600^4
Solution found! 20615673^4=15365639^4+18796760^4+2682440^4  (primitive)
Solution found! 20615673^4=15365639^4+2682440^4+18796760^4  (primitive)
Solution found! 7182177^4=3697823^4+7047520^4+1628600^4
Solution found! 30841113^4=15878887^4+30262880^4+6993400^4
Solution found! 101817921^4=52422079^4+99908960^4+23087800^4
Solution found! 12197457^4=8282543^4+11289040^4+5870000^4  (primitive)
Solution found! 34220961^4=17619039^4+33579360^4+7759800^4
Solution found! 57879897^4=29800103^4+56794720^4+13124600^4
Solution found! 81538833^4=41981167^4+80010080^4+18489400^4
Solution found! 16003017^4=4479031^4+14173720^4+12552200^4  (primitive)
Solution found! 422481^4=217519^4+414560^4+95800^4  (primitive)
Solution found! 16430513^4=16281009^4+3642840^4+7028600^4  (primitive)
Solution found! 47740353^4=24579647^4+46845280^4+10825400^4
Solution found! 37600809^4=19359191^4+36895840^4+8526200^4


评论


\ $ \ begingroup \ $
不幸的是,引用的euler413.c程序格式错误,无法编译。
\ $ \ endgroup \ $
–爱德华
16年11月4日在15:39

\ $ \ begingroup \ $
@爱德华:是的。该文件错误地包含了该程序两次。从行1438到最后一行删除所有内容。
\ $ \ endgroup \ $
– miracle173
16年4月4日在16:45

\ $ \ begingroup \ $
@Edward在Robert Gerbicz的页面上也有用于Windows的编译版本euler413.exe。我在文本中添加了一些注释。
\ $ \ endgroup \ $
– miracle173
16年11月4日在17:49

\ $ \ begingroup \ $
我很欣赏这个想法,但是第1438行是我下载的最后一行,出于安​​全原因,我有一项政策,禁止从互联网运行随机可执行文件。
\ $ \ endgroup \ $
–爱德华
16年11月4日在18:29

\ $ \ begingroup \ $
代码为我编译,但是要使其在64位系统上工作,您需要更改五行乘法器17 =(unsigned int **)(malloc)(17 * 17 * sizeof(unsigned int)) ;依此类推,将sizeof(unsigned int *)替换为sizeof(unsigned int *)(以下四行类似)。
\ $ \ endgroup \ $
– Alex Selby
16年11月4日在23:55

#12 楼

我想我有一个\ $ O(n ^ 2)\ $解决方案,本质上需要\ $ O(n ^ 2)\ $空间,但是得到了非常大的恒定因子(〜100)缩小。这对于第一个\ $ d = 422481 \ $应该是现实的。 ^ 4 = d ^ 4 $$

(\ $ a \ $和\ $ b \ $是偶数,\ $ c \ $和\ $ d \ $是奇数)

使用Staycator的mod物品

\ $ 0 + 0 + 1 = 1 \ $,\ $ a ^ 4 \ $和\ $ b ^ 4 \ $以0000和\ $ c \ $结尾和\ $ d \ $以1,3,7或9结尾。

由于\ $ a ^ 4 + b ^ 4 = d ^ 4-c ^ 4 \ $,\ $ c \ $和\ $ d \ $以相同的四位数结尾。

\ $ 0 + 6 + 5 = 1 \ $,\ $ a ^ 4 \ $以0000结尾和\ $ c ^ 4 \ $在0625中。\ $ b \ $以2、4、6或8结尾,而d以1、3、7或9结尾。

因为\ $ a ^ 4 + c ^ 4 = d ^ 4 -b ^ 4 \ $,\ $ d ^ 4-b ^ 4 \ $的后四位数字是0625。

解决方案

(我没有写这个到代码中,只是记事本中的一些注释)


以\ $ n \ $ 1到500k预先计算\ $ n ^ 4 \ $
按\ $的最后一位n \ $
bin \ $ n ^ 4 \ $,其中n以\ $ n ^ 4 \ $
Bin \ $ n ^ 4 \ $的后四位数结尾,1、3、7或9其中\ $ n \ $以\ $ n ^ 4 \ $
存储哈希值的后四位以2、4、6或8结尾\ $ a ^ 4 + b ^ 4 \ $的表,其中\ $ a \ $和\ $ b \ $都以零结尾
最后四个奇数循环并比较可能的\ $ d ^ 4-c ^ 4 \ $散列到存储的\ $ a ^ 4 + b ^ 4 \ $
存储\ $ a ^ 4 + c ^ 4 \ $的哈希表,其中\ $ a ^ 4 \ $以0000和\ $ c结尾在0625年^ 4 \ $
查找垃圾箱,其中奇数\ $ n \ $减去偶数\ $ n \ $垃圾箱= 0625(环绕)。比较
可能的\ $ d ^ 4-b ^ 4 \ $散列到存储的\ $ a ^ 4 + c ^ 4 \ $。


#13 楼

假设计算\ $ a ^ 4 + b ^ 4 \ $和\ $ d ^ 4-c ^ 4 \ $并找到相等的值。假设\ $ a \ ge b \ $和\ $ d \ gt c \ $。

创建两个优先级队列,一个用于值\ $ a ^ 4 + b ^ 4 \ $,一个用于值\ $ d ^ 4-c ^ 4 \ $,但是还要跟踪哪个使用了\ $ b \ $和\ $ c \ $。用其中\ $ b = 0 \ $,\ $ c = d-1 \ $的值填充优先级队列,并为每个\ $ a,d \ $跟踪您已放入队列中的数字(最初为\ $ b = 0 \ $,\ $ c = d-1 \ $)。如果每个队列中的最小数目相同,那么您有解决方案。如果不是,则从队列中删除(\ $ a,b \ $),然后插入(\ $ a,b + 1 \ $),除非\ $ b + 1 \ gt a \ $;或者您从队列中删除(\ $ d,c \ $)并插入(\ $ d,c-1 \ $),除非c =0。

涉及的数字将很大,并且为64位整数可能还不够。但是,除非结果大于\ $ 2 ^ {112} \ $(a,b直至\ $ 2 ^ {28} \ $),否则您可以以双精度计算一次并使用无符号64位计算结果。双精度将为您提供大致正确的结果。无符号的64位将为您提供结果的确切最后64位。由于\ $ d,c \ $将更大,因此将\ $ d ^ 4-c ^ 4 \ $计算为\ $(d ^ 2 + c ^ 2)(d ^ 2-c ^ 2)\ $。

这将只占用\ $ O(n)\ $空间,因此您不太可能计算足够远,因此这成为一个问题。

评论


\ $ \ begingroup \ $
最初,您具有\ $ a ^ 4 + b ^ 4 \ $的最小可能值,但\ $ d ^ 4-c ^ 4 \ $的最大可能值–您需要对该方法进行一些认真的改进才能让您开始从考虑中消除任何对。
\ $ \ endgroup \ $
– Ben Voigt
16-10-27在19:08

#14 楼

本着实际上优化算法本身的精神,而忽略了其他人已经正确提及的非常实际的溢出问题,请考虑一下这里发生的情况:

for (a = 1; a < 100000; a++) {
    for (b = 1; b < 300000; b++) {
        for (c = 1; c < 500000; c++) {
            for (d = 1; d < 500000; d++) {
                if (prop(a, b, c, d))
                    printf("FOUND IT!\na = %ld\nb = %ld\nc = %ld\nd = %ld\n", a, b, c, d);
            }
        }
    }
}
即使在a上进行了更改,bc在最内层循环的每次迭代上都被提高到四次方。意图)无缘无故地进行数百万次运算,因此也无需在每次迭代中求等式的左侧。

这将大大减少计算时间:

for (a = 1; a < 100000; a++) {
    long int a4 = a * a * a * a;
    for (b = 1; b < 300000; b++) {
        long int a4b4 = a4 + b * b * b * b;
        for (c = 1; c < 500000; c++) {
            long int a4b4c4 = a4b4 + c * c * c * c;
            for (d = 1; d < 500000; d++) {
                if (a4b4c4 == d * d * d * d)
                    printf("FOUND IT!\na = %ld\nb = %ld\nc = %ld\nd = %ld\n", a, b, c, d);
            }
        }
    }
}


我没有花时间进行基准测试,所以我不能准确地告诉您您将看到什么样的改进,但是我将其留给读者练习。

#15 楼


我可以采取什么策略使上述代码更快地运行?


节省时间的人


简单:最多可以识别, a = 1 to 95800b = a to 217519c = b to 414560d = c+1 or more组合需要尝试。组合使用b = 1 to a-1c = 1 to b-1是多余的。 d = 1 to c无法求解方程。 @phoog
设置ab的测试值后,通过注意cd中的较低者,可以通过递增cd来非常有效地测试sum_ab + c**4d**4的值。
请注意,如果a, b, c, d是解决方案,则n*a, n*b, n*c, n*d也是解决方案。这意味着代码无需检查a,b,c,d都为零的解决方案,因为将找到具有较小值的奇数值的解决方案。
整数的4的幂均具有00000001的最低有效位。将它们中的任何3相加必须等于0或1(d的最低有效半字节)。这意味着要么全部都是偶数(我们可以从上面忽略),要么a,b,c的1必须为奇数,其他2个偶数和d必须为奇数。使用此快捷方式可以线性减少运行时间。
该任务使用的是超过64位的整数数学。 C没有指定比64位宽的标准类型。各种平台都有更广泛的类型,例如gcc上的__uint128

O(n3)时间解决方案。 O(n1)内存。
随着越来越多的a,b,c,d范围的尝试,下面的代码在时间上表现出明显的O(n3)趋势。 100%通过:5,900,000秒(69天)。

// Ref 
a = 95800; b = 217519; c = 414560; d = 422481


#include <assert.h>
#include <inttypes.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define a (95800 + 1)
#define b (217519 + 1)
#define c (414560 + 1)
#define d (422481 + 1)

// Adjust to some 128 bit type on your platform
// Can leave it at 64-bit if `num/den < 65535/d` or about 0.155 for initial testing
typedef uintmax_t uint128t;

#define pow2(x) ((uint128t)(x)*(x))
#define pow4(x) (pow2((uint_fast64_t)(x)*(x)))
void doit_times(unsigned num, unsigned den) {
  time_t t0;
  time(&t0);
  printf("Portion:%6g%%\t", 100.0 * num / den);
  uint_fast32_t a_max = (uint_fast32_t) round(1.0 * a * num / den);
  uint_fast32_t b_max = (uint_fast32_t) round(1.0 * b * num / den);
  uint_fast32_t c_max = (uint_fast32_t) round(1.0 * c * num / den);
  uint_fast32_t d_max = (uint_fast32_t) round(1.0 * d * num / den);
  printf("a_max:%6" PRIuFAST32 " b_max:%6" PRIuFAST32 //
      " c_max:%6" PRIuFAST32 " d_max:%6" PRIuFAST32 "\t", //
      a_max, b_max, c_max, d_max);
  uint_fast32_t q0 = 1;
  for (uint_fast32_t qa = q0; qa <= a_max; qa++) {
    assert(qa <= 0xFFFF || sizeof(uint128t) > 4);
    uint128t qa4 = pow4(qa);
    for (uint_fast32_t qb = qa; qb <= b_max; qb++) {
      uint128t sum_ab4 = qa4 + pow4(qb);
      uint_fast32_t qc = qb;
      uint128t sum_abc4 = sum_ab4 + pow4(qc);
      uint_fast32_t qd = qc + 1;
      uint128t qd4 = pow4(qd);

      // Loop until `c` to too large.
      for (;;) {
        if (sum_abc4 < qd4) {
          if (qc >= c_max) break;
          qc++;
          sum_abc4 = sum_ab4 + pow4(qc);
        } else if (sum_abc4 > qd4) {
          //if (qd >= d_max) break;
          qd++;
          qd4 = pow4(qd);
        } else {
          puts("Success!");
          printf("a %" PRIuFAST32 "\n", qa);
          printf("b %" PRIuFAST32 "\n", qb);
          printf("c %" PRIuFAST32 "\n", qc);
          printf("d %" PRIuFAST32 "\n", qd);
          fflush(stdout);
          exit(0);
        }
      }

    }
  }
  time_t t1;
  time(&t1);
  double dt = difftime(t1, t0);
  printf("t:%g\n", dt);
  fflush(stdout);
}

int main(void) {
  for (unsigned num = 1; num <= 1000; num++) {
    doit_times(num, 1000);
  }
  puts("Done");
  return -1;
}


输出

Portion:   0.1% a_max:    96 b_max:   218 c_max:   415 d_max:   422 t:0
Portion:   0.2% a_max:   192 b_max:   435 c_max:   829 d_max:   845 t:0
Portion:   0.3% a_max:   287 b_max:   653 c_max:  1244 d_max:  1267 t:1
Portion:   0.4% a_max:   383 b_max:   870 c_max:  1658 d_max:  1690 t:1
Portion:   0.5% a_max:   479 b_max:  1088 c_max:  2073 d_max:  2112 t:2
Portion:   0.6% a_max:   575 b_max:  1305 c_max:  2487 d_max:  2535 t:3
Portion:   0.7% a_max:   671 b_max:  1523 c_max:  2902 d_max:  2957 t:5
Portion:   0.8% a_max:   766 b_max:  1740 c_max:  3316 d_max:  3380 t:6
Portion:   0.9% a_max:   862 b_max:  1958 c_max:  3731 d_max:  3802 t:10
Portion:     1% a_max:   958 b_max:  2175 c_max:  4146 d_max:  4225 t:12
Portion:   1.1% a_max:  1054 b_max:  2393 c_max:  4560 d_max:  4647 t:16
Portion:   1.2% a_max:  1150 b_max:  2610 c_max:  4975 d_max:  5070 t:21
Portion:   1.3% a_max:  1245 b_max:  2828 c_max:  5389 d_max:  5492 t:25
Portion:   1.4% a_max:  1341 b_max:  3045 c_max:  5804 d_max:  5915 t:32
Portion:   1.5% a_max:  1437 b_max:  3263 c_max:  6218 d_max:  6337 t:39
Portion:   1.6% a_max:  1533 b_max:  3480 c_max:  6633 d_max:  6760 t:48
Portion:   1.7% a_max:  1629 b_max:  3698 c_max:  7048 d_max:  7182 t:56
Portion:   1.8% a_max:  1724 b_max:  3915 c_max:  7462 d_max:  7605 t:67
Portion:   1.9% a_max:  1820 b_max:  4133 c_max:  7877 d_max:  8027 t:77
Portion:     2% a_max:  1916 b_max:  4350 c_max:  8291 d_max:  8450 t:91
Portion:   2.1% a_max:  2012 b_max:  4568 c_max:  8706 d_max:  8872 t:104
Portion:   2.2% a_max:  2108 b_max:  4785 c_max:  9120 d_max:  9295 t:120


请注意测试d不需要在限制内,因为c首先或之后不久就超过了限制。

#16 楼

关于\ $ D \ $是否为四次幂的检查,您是否不能计算\ $ D \ $的所有整数四次幂直到一个极限,然后将它们存储在哈希表中?这样一来,任何潜在的\ $ D ^ 4 \ $的检查时间都将更改为\ $ O(1)\ $,一开始的成本就增加了。显然,您不能为任意大的\ $ D \ $实现此功能,但是OP不会检查该值(\ $ A,B,C \ $的硬编码限制),但是大小约为0.5M的哈希表是很好的选择

如果要检查有界\\ A,B,C \ $,则\\ D \ $的上限可以从\ $ \ lceil { \ sqrt [4] {A ^ 4 + B ^ 4 + C ^ 4}} \ rceil \ $,该操作仅涉及搜索的第4个root操作(不包括验证)。

评论


\ $ \ begingroup \ $
如果循环的顺序使得a> = b> = c,则d在[a; pow(3,0.25)* a]。
\ $ \ endgroup \ $
– chqrlie
16-10-28在22:33

\ $ \ begingroup \ $
好点。节省一点内存。
\ $ \ endgroup \ $
–单性狂
16-10-29在0:33

#17 楼

如何先找到幂然后计算等效性呢?这将消除对同一个数字的四次幂的重新计算(由于空间原因)。

可以这样编写:

/>也可以使用其他优化方法。

评论


\ $ \ begingroup \ $
似乎没有改善原始代码的内存使用或预期的运行时间。
\ $ \ endgroup \ $
–CiaPan
16-10-28在7:35

\ $ \ begingroup \ $
为什么?在原始代码中,作者四次计算相同数字的四次幂,例如分别用于a,b,c和d。上面的代码仅计算一次四次方,并在必要时使用计算出的值。可能没有内存优化。但我的首要任务是速度,而不是这里的记忆。
\ $ \ endgroup \ $
– Tempx
16年11月1日在6:15

\ $ \ begingroup \ $
预先计算4阶幂不会为您节省很多。您的代码将执行\ $ 5000 ^ 4 = 625 \ cdot 10 ^ {16} \ $迭代,就像原始代码一样。即使您将每个迭代时间设为原始迭代的\ $ 1/100 \ $,整个执行仍将花费比任何人等待的时间更长的时间。请参阅Tavian Barnes的评论。
\ $ \ endgroup \ $
–CiaPan
16年11月1日在17:07