0461 圆周率近似
* * * *
拉格朗日计划
* * * *
圆周率近似

令$f_n(k)=e^{k/n}-1$,其中k,n都是非负整数。

可以验证$f_{200}(6)+f_{200}(75)+f_{200}(89)+f_{200}(226)=3.141592644529\ldots\approx\pi$。

事实上这也是$n=200$时,形如$f_n(a)+f_n(b)+f_n(c)+f_n(d)$的式子对$\pi$的最好近似。

令$g(n)=a^2+b^2+c^2+d^2$,其中$a, b, c, d$是能使$|f_n(a)+f_n(b)+f_n(c)+f_n(d)-\pi|$最小的值。

例如$g(200)=6^2+75^2+89^2+226^2=64658$。

求$g(10000)$。

本题难度:



解答

本题就算法而言很简单,即meet in the middle法。

先遍历k,生成所有不超过$\pi$的$f_n(k)$,并将这些值保存在列表f中。

再遍历f,保存所有不超过$\pi$的$f_n(i)+f_n(j)$的值在列表g中,并将g排序。

最后遍历g,对其中每一值,二分查找表中令一项,使两者之和最接近$\pi$。

本题的主要性能瓶颈在于空间,因列表g有近八千万项,因此难以同时保存$i^2+j^2$的值,需要在找出最值后再双循环遍历一次f以获得对应的i,j。

最终结果是159820276,相应的a,b,c,d分别是1433, 2147, 4903, 1136。

注:为优化内存使用,以下代码为Python 3,用时若干分钟,需耐心等待。

import math,bisect
n=10000
f=[]
k=0
while (t:=(math.exp(k/n)-1))<=math.pi:
    f.append(t)
    k+=1

print(f"length of f is {len(f)}")

g=[f[i]+f[j] for i in range(len(f)) for j in range(i,len(f)) if f[i]+f[j]<=math.pi]
g.sort()

print(f"length of f+f is {len(g)}")

i=0
j=len(g)
m=math.pi
mi=i
mj=j
while i < len(g) and i <= j:
    j=bisect.bisect(g,math.pi-g[i])
    if j < len(g) and 0 <= g[i]+g[j]-math.pi < m:
        m=g[i]+g[j]-math.pi
        mi=i
        mj=j
    j-=1
    if j>=0 and 0 < math.pi-g[i]-g[j] < m:
        m=math.pi-g[i]-g[j]
        mi=i
        mj=j
    i+=1

d=[(i,j,i*i+j*j) for i in range(len(f)) for j in range(i,len(f)) if f[i]+f[j]==g[mi] or f[i]+f[j]==g[mj]]

print("a,b,c,d=",d[0][0],d[0][1],d[1][0],d[1][1])
print(d[0][2]+d[1][2])