这都是关于代数的,与 Clojure 或编程无关。由于本站不支持数学排版,所以我们用 Clojure 来表达。
定义
(defn sigma [coll] (reduce + coll))
和
(defn sigma-1-to-n [f n]
(sigma (map f (rest (range (inc n))))))
(或
(defn sigma-1-to-n [f n]
(->> n inc range rest (map f) sigma))
)
那么问题是,给定n,找到i 使得(= (sigma-1-to-n #(* % % %) i) n)。
快速执行此操作的关键是 Faulhaber's formula 用于立方体。它告诉我们,对于任何自然数i,以下是相等的:
(#(*' % %) (sigma-1-to-n identity i))
(sigma-1-to-n #(* % % %) i)
(#(*' % %) (/ (*' i (inc i)) 2))
所以,作为立方体的总和,数字
- 必须是完美的正方形
- 其平方根是前这么多数字的总和。
为了确定一个整数是否是一个完美的平方,我们取它的近似浮点平方根,看看对最接近的整数求平方是否能恢复我们的整数:
(defn perfect-square-root [n]
(let [candidate (-> n double Math/sqrt Math/round)]
(when (= (*' candidate candidate) n)
candidate)))
如果参数不是完美正方形,则返回 nil。
现在我们有了平方根,我们必须确定它是否是一系列自然数的和:在普通代数中,是(j (j + 1)) / 2,对于一些自然数j。
我们可以使用类似的技巧直接回答这个问题。
j (j + 1) = (j + 1/2)^2 + 1/4
因此,如果有一个,则以下函数返回与参数相加的连续数字的数量:
(defn perfect-sum-of [n]
(let [j (-> n (*' 2)
(- 1/4)
double
Math/sqrt
(- 0.5)
Math/round)]
(when (= (/ (*' j (inc j)) 2) n)
j)))
我们可以结合这些来做你想做的事:
(defn find-n [big-i]
{:pre [(integer? big-i) ((complement neg?) big-i)]}
(let [sqrt (perfect-square-root big-i)]
(and sqrt (perfect-sum-of sqrt))))
(def sum 1025247423603083074023000250000N)
(time (find-n sum))
"Elapsed time: 0.043095 msecs"
=> 45001000
(请注意,时间比以前快了大约二十倍,可能是因为 HotSpot 必须在 find-n 上工作,已被附加测试彻底锻炼)
这显然比原来的要快很多。
警告
由于浮点的有限精度,我担心上述过程可能会产生假阴性(它永远不会产生假阳性)。然而,测试表明该过程对于问题使用的数字类型是牢不可破的。
Java double 有 52 位精度,大约小数点后 15.6 位。令人担忧的是,如果数字远大于此,该过程可能会错过精确的整数解,因为舍入只能与它开始的浮点数一样准确。
但是,该过程正确地解决了 31 位整数的示例。用许多(一千万!)类似的数字进行测试不会导致失败。
为了测试解决方案,我们生成了一个(惰性)[limit cube-sum] 对序列:
(defn generator [limit cube-sum]
(iterate
(fn [[l cs]]
(let [l (inc l)
cs (+' cs (*' l l l))]
[limit cs]))
[limit cube-sum]))
例如,
(take 10 (generator 0 0))
=> ([0 0] [1 1] [2 9] [3 36] [4 100] [5 225] [6 441] [7 784] [8 1296] [9 2025])
现在我们
- 从给定的例子开始,
- 尝试接下来的一千万个案例,然后
- 删除有效的。
所以
(remove (fn [[l cs]] (= (find-n cs) l)) (take 10000000 (generator 45001000 1025247423603083074023000250000N)))
=> ()
它们都有效。没有失败。只是为了确保我们的测试有效:
(remove (fn [[l cs]] (= (find-n cs) l)) (take 10 (generator 45001001 1025247423603083074023000250000N)))
=>
([45001001 1025247423603083074023000250000N]
[45001002 1025247514734170359564546262008N]
[45001003 1025247605865263720376770289035N]
[45001004 1025247696996363156459942337099N]
[45001005 1025247788127468667814332412224N]
[45001006 1025247879258580254440210520440N]
[45001007 1025247970389697916337846667783N]
[45001008 1025248061520821653507510860295N]
[45001009 1025248152651951465949473104024N]
[45001010 1025248243783087353664003405024N])
一切都应该失败,而他们确实如此。