欧拉计划 Project Euler65(e的有理逼近)题解
欧拉计划 Project Euler 65 题解
- 题干
- e的有理逼近
- 思路
- code
题干
e的有理逼近
2的算术平方根可以写成无限连分数的形式
2 = 1 + 1 2 + 1 2 + 1 2 + 1 2 + … \sqrt{2} = 1 + \frac{1}{2 + \frac{1}{2 + \frac{1}{2 + \frac{1}{2 + \dots}}}} 2=1+2+2+2+2+…1111
这个无限连分数可以简记为 2 = [ 1 ; ( 2 ) ] \sqrt{2} = [1; (2)] 2=[1;(2)],其中 ( 2 ) (2) (2)表示2无限重复。同样的,我们可以记
23 = [ 4 ; ( 1 , 3 , 1 , 8 ) ] \sqrt{23} = [4;(1, 3, 1, 8)] 23=[4;(1,3,1,8)]
可以证明,截取算术平方根连分数表示的一部分所组成的序列,给出了一系列最佳有理逼近值。让我们
来考虑 2 \sqrt{2} 2的逼近值
1 + 1 2 = 3 2 1 + \frac{1}{2} = \frac{3}{2} 1+21=23
1 + 1 2 + 1 2 = 3 5 1 + \frac{1}{2 + \frac{1}{2}} = \frac{3}{5} 1+2+211=53
1 + 1 2 + 1 2 + 1 2 = 17 12 1 + \frac{1}{2 + \frac{1}{2 + \frac{1}{2}}} = \frac{17}{12} 1+2+2+2111=1217
1 + 1 2 + 1 2 + 1 2 + 1 2 = 41 29 1 + \frac{1}{2 + \frac{1}{2 + \frac{1}{2 + \frac{1}{2}}}} = \frac{41}{29} 1+2+2+2+21111=2941
因此 2 \sqrt{2} 2的前十个逼近值为:
1 , 3 2 , 7 5 , 17 12 , 41 29 , 99 70 , 239 169 , 577 408 , 1393 985 , 3363 2378 , … 1, \frac{3}{2}, \frac{7}{5}, \frac{17}{12}, \frac{41}{29}, \frac{99}{70}, \frac{239}{169}, \frac{577}{408}, \frac{1393}{985}, \frac{3363}{2378},\dots 1,23,57,1217,2941,7099,169239,408577,9851393,23783363,…
最令人惊讶的莫过于重要的数学常数 e e e有如下连分数表示
e = [ 2 ; 1 , 2 , 1 , 1 , 4 , 1 , 1 , 6 , 1 , … , 1 , 2 k , 1 ] e = [2;1,2,1,1,4,1,1,6,1,\dots,1,2k,1] e=[2;1,2,1,1,4,1,1,6,1,…,1,2k,1]
e e e的前十个逼近值为:
2 , 3 , 8 3 , 11 4 , 19 7 , 87 32 , 106 39 , 193 71 , 1264 465 , 1457 536 , … 2, 3, \frac{8}{3}, \frac{11}{4}, \frac{19}{7}, \frac{87}{32}, \frac{106}{39}, \frac{193}{71}, \frac{1264}{465}, \frac{1457}{536},\dots 2,3,38,411,719,3287,39106,71193,4651264,5361457,…
第 10 10 10个逼近值的分子各位数字之和为 1 + 4 + 5 + 7 = 17 1 + 4 + 5 + 7 = 17 1+4+5+7=17
求 e e e的第100个逼近值的分子各位数字之和
思路
e e e的连分数表示为:
e = [ 2 ; 1 , 2 , 1 , 1 , 4 , 1 , 1 , 6 , 1 , … , 1 , 2 k , 1 ] e = [2;1,2,1,1,4,1,1,6,1,\dots,1,2k,1] e=[2;1,2,1,1,4,1,1,6,1,…,1,2k,1]
这意味着系数序列 a 0 , a 1 , a 2 , … a_{0}, a_{1}, a_{2}, \dots a0,a1,a2,…为:
a 0 = 2 , a 1 = 1 , a 2 = 2 , a 3 = 1 , a 4 = 1 , a 5 = 4 , a 6 = 1 , a 7 = 1 , a 8 = 6 , a 9 = 1 , … a_{0} = 2, a_{1} = 1, a_{2} = 2, a_{3} = 1, a_{4} = 1, a_{5} = 4, a_{6} = 1, a_{7} = 1, a_{8} = 6, a_{9} = 1, \dots a0=2,a1=1,a2=2,a3=1,a4=1,a5=4,a6=1,a7=1,a8=6,a9=1,…
对于 n ≥ 1 n\geq1 n≥1,序列的模式是 1 , 2 k , 1 1, 2k, 1 1,2k,1重复,更准确度说
- 如果 n ( m o d 3 ) = 1 o r n ( m o d 3 ) = 0 n\:(mod\:3)\:=\:1 \;or \: n\:(mod\:3)\:=\:0 n(mod3)=1orn(mod3)=0,则 a n = 1 a_{n} = 1 an=1
- 如果 n ( m o d 3 ) = 2 n\:(mod\:3)\:=\:2 n(mod3)=2,则 a n = 2 k a_{n} = 2k an=2k, 其中 k = ( n + 1 ) / 3 k = (n + 1) / 3 k=(n+1)/3
连分数的 n n n个逼近值(从第 0 0 0个开始计数)为 p n q n \frac{p_{n}}{q_{n}} qnpn,其分子 p n p_{n} pn和分母 q n q_{n} qn
可以通过以下递推式进行计算
- p n = a n p n − 1 + p n − 2 p_{n} = a_{n}p_{n-1} + p_{n-2} pn=anpn−1+pn−2
- q n = a n q n − 1 + q n − 2 q_{n} = a_{n}q_{n-1} + q_{n-2} qn=anqn−1+qn−2
初始条件为(因为从0开始算,所以这边有负数但实际编程的时候是不需要的)
- p − 2 = 0 , p − 1 = 1 p_{-2} = 0, p_{-1} = 1 p−2=0,p−1=1
- q − 2 = 1 , q − 1 = 0 q_{-2} = 1, q_{-1} = 0 q−2=1,q−1=0
我们可以带几个值进去验证一下
- p 0 = a 0 p − 1 + p − 2 = 2 p_{0} = a_{0}p_{-1} + p_{-2} = 2 p0=a0p−1+p−2=2
- p 1 = a 1 p 0 + p − 1 = 3 p_{1} = a_{1}p_{0} + p_{-1} = 3 p1=a1p0+p−1=3
- p 2 = a 2 p 1 + p 0 = 8 p_{2} = a_{2}p_{1} + p_{0} = 8 p2=a2p1+p0=8
- p 3 = a 3 p 2 + p 1 = 11 p_{3} = a_{3}p_{2} + p_{1} = 11 p3=a3p2+p1=11
有了以上思路后不难写出代码,如果使用c++编程要记得模拟大数加法和乘法,不然会溢出,使用python则不需要。
code
// 272
#include <bits/stdc++.h>using namespace std;using ll = long long;// 大数加法
string add(string num1, string num2) {if (num1 == "0") return num2;if (num2 == "0") return num1;string ans = "";int i = num1.length() - 1;int j = num2.length() - 1;int carry = 0; // 进位while (i >= 0 || j >= 0 || carry) {int d1 = (i >= 0) ? (num1[i--] - '0') : 0;int d2 = (j >= 0) ? (num2[j--] - '0') : 0;int cur = d1 + d2 + carry;ans += to_string(cur % 10);carry = cur / 10;}reverse(ans.begin(), ans.end());return ans.empty() ? "0" : ans;
}// 大数乘法
// 一种显然的思路是调用加法 但是当数字很大时就会很慢
// 所以我们手动模拟一下大数乘 这里涉及到的比较小
string mul(string num, int mulp) {if (num == "0" || mulp == 0) {return "0";}if (mulp == 1) {return num;}string ans = "";int carry = 0; // 进位for (int i = num.length() - 1; i >= 0; --i) {int di = num[i] - '0';ll pr = (long long)di * mulp + carry;ans += to_string(pr % 10);carry = pr / 10;}// 处理可能剩余的最高位进位while (carry > 0) {ans += to_string(carry % 10);carry /= 10;}reverse(ans.begin(), ans.end());return ans.empty() ? "0" : ans;
}void solve() {vector<int> a(100);a[0] = 2;for (int n = 1; n < 100; ++n) {if (n % 3 == 2) {int k = (n + 1) / 3;a[n] = 2 * k;} else {a[n] = 1;}}string pmin2 = "0"; // p_{i - 2}string pmin1 = "1"; // p_{i - 1};string p_n = "0"; // 当前p_{i}for (int i = 0; i < 100; ++i) {int cur = a[i];string ter1 = mul(pmin1, cur);p_n = add(ter1, pmin2);pmin2 = pmin1;pmin1 = p_n;}long long ans = 0;for (char di : p_n) {ans += (di - '0');}cout << ans << "\n";}int main() {ios::sync_with_stdio(false);cin.tie(nullptr);int tt = 1; // cin >> tt;while (tt--) {solve();}return 0;
}