欧拉计划 61~70

61

开幕雷击,其实我们可以利用 BFS 分层搜索,然后看能不能构成循环就行了。对于图中的节点,我们只用记录前两位和后两位,因为成环之后,每个数字都出现了 \(2\) 次(最高位和最低位),因此答案乘 \(101\) 即可,跑得非常快。

// cpp
#include<bits/stdc++.h>
using namespace std;
vector<int> edge[6][100];

void create() {
    for (int n = 1; n; n ++ ) {
        int p = n * (n + 1) / 2;
        if (p >= 10000) break;
        if (p / 100 == 0 || p % 100 == 0) continue;
        if (p > 1000) edge[0][p / 100].push_back(p % 100);
    }
    for (int n = 1; n; n ++ ) {
        int p = n * n;
        if (p >= 10000) break;
        if (p / 100 == 0 || p % 100 == 0) continue;
        if (p > 1000) edge[1][p / 100].push_back(p % 100);
    }
    for (int n = 1; n; n ++ ) {
        int p = n * (3 * n - 1) / 2;
        if (p >= 10000) break;
        if (p / 100 == 0 || p % 100 == 0) continue;
        if (p > 1000) edge[2][p / 100].push_back(p % 100);
    }
    for (int n = 1; n; n ++ ) {
        int p = n * (2 * n - 1);
        if (p >= 10000) break;
        if (p / 100 == 0 || p % 100 == 0) continue;
        if (p > 1000) edge[3][p / 100].push_back(p % 100);
    }
    for (int n = 1; n; n ++ ) {
        int p = n * (5 * n - 3) / 2;
        if (p >= 10000) break;
        if (p / 100 == 0 || p % 100 == 0) continue;
        if (p > 1000) edge[4][p / 100].push_back(p % 100);
    }
    for (int n = 1; n; n ++ ) {
        int p = n * (3 * n - 2);
        if (p >= 10000) break;
        if (p / 100 == 0 || p % 100 == 0) continue;
        if (p > 1000) edge[5][p / 100].push_back(p % 100);
    }
}

void search() {
    int reflection[5] = {1, 2, 3, 4, 5};
    do {
        queue<pair<int, pair<int, int>>> q;
        for (int start = 1; start < 100; start ++ ) {
            for (auto p : edge[0][start]) {
                q.push({start, {p, p}});
            }
        }
        for (int i = 0; i < 5; i ++ ) {
            queue<pair<int, pair<int, int>>> tmp;
            while (q.size()) {
                auto t = q.front(); q.pop();
                for (auto nxt : edge[reflection[i]][t.second.first]) {
                    tmp.push({t.first, {nxt, t.second.second + nxt}});
                }
            }
            swap(q, tmp);
        }
        while (q.size()) {
            auto t = q.front(); q.pop();
            if (t.first == t.second.first) cout << t.second.second * 101 << "\n";
        }
    } while (next_permutation(reflection, reflection + 5));
}

int main() {
    create();
    search();
    return 0;
}

62

暴力出奇迹。

# python
dic = {''.join(sorted(str(pow(i, 3)))) : 0 for i in range(10000)}
for i in range(10000):
    dic[''.join(sorted(str(pow(i, 3))))] += 1

for i in range(10000):
    if dic[''.join(sorted(str(pow(i, 3))))] == 5:
        print(pow(i, 3))
        break

63

由于 \(len = [\lg x] + 1\),且 \(\lg x^n = n\lg x\),所以底数显然小于 \(10\),而 \(n \lg x \ge n - 1\) 告诉我们 \(x^{\frac{n}{n - 1}} \ge 10\),取 \(x = 9\)\(n\) 的上界为 \(21\),所以暴力算就行。

# python
ans = 0
for i in range(1, 22):
    for j in range(1, 10):
        if len(str(pow(j, i))) == i:
            ans += 1
print(ans)

64

神秘模拟,我们考虑到对一个数 \(x\)\(\sqrt{x}\) 总是出现在它的上部,所以直接不管那一部分,记录分母和分子上的有理数,模拟倒数的过程,如果相同的分数出现两次则出现循环。

// cpp
#include<bits/stdc++.h>
using namespace std;

int continued_fraction(int x) {
    int len = 0, fraction = 1, sqr = -sqrt(x), res = sqr;
    if (res * res == x) return 0;
    map<pair<int, int>, int> mp;
    while (1) {
        len ++ ;
        int _fraction = x - res * res;
        int d = __gcd(_fraction, fraction);
        _fraction /= d, fraction /= d;
        res = (-res * fraction % _fraction - sqr) % _fraction + sqr;
        fraction = _fraction;
        if (mp.count({res, fraction})) return len - mp[{res, fraction}];
        mp[{res, fraction}] = len;
    }
}

int main() {
    int ans = 0;
    for (int i = 1; i <= 10000; i ++ ) {
        if (continued_fraction(i) & 1) {
            ans ++ ;
        }
    }
    cout << ans << "\n";
    return 0;
}

65

连分数怎么这么坏啊,还是 python 好用。

// cpp
from fractions import *

e = [2, 1]
for i in range(98):
    if i % 3 == 0: e.append((i // 3 + 1) * 2)
    else: e.append(1)
e = e[::-1]

ans = Fraction(e[0])
e.pop(0)
for i in e:
    ans = 1 / ans + i
print(sum(map(int, str(ans.numerator))))

66

考虑利用连分数求解佩尔方程。

定理1

\(sqrt{D}\) 的连分数分解满足

\[D = [a_0 ; (a_1, \, a_2, \, \cdots, \, a_{n - 1}, \, 2a_0)] \]

即循环节的最后一位恰好是第一位的两位。

基于该定理,我们可以较为简便的写出连分数展开。

定理2

\[\frac{p}{q} = [a_0 ; a_1, \, a_2, \, \cdots, \, a_{n - 1}] \]

当连分数展开的循环节长度为奇数时,佩尔方程有最小解:

\[x = 2p^2 + 1, \, y = 2pq \]

当连分数展开的循环节长度为偶数时,佩尔方程有最小解:

\[x = p, \, y = q \]

利用该定理,我们可以在求解出连分数展开的循环节后,方便的求出最小解。

接下来我们着手连分数的展开。

考虑到每一级展开时连分数的式子满足:

\[\begin{aligned}r_i &= \frac{\sqrt{D} + b_i}{c_i} \end{aligned} \]

因此其可提出一个常数:

\[a_i = \lfloor r_i \rfloor \]

考虑下一级解的形态:

\[\begin{aligned}r_{i + 1} &= \frac{1}{\frac{\sqrt{D} + b_i}{c_i} - a_i} \\ &= \frac{c_i}{\sqrt{D} + b_i - a_ic_i} \\ &= \frac{c_1(\sqrt{D} - b_i + a_ic_i)}{D - (b_i - a_ic_i)^2}\end{aligned}\]

不难看出

\[\begin{cases}b_{i + 1} = a_ic_i - b_i \\ c_{i + 1} = \frac{D - (b_i - a_ic_i)^2}{c_i} = \frac{D - b_{i + 1}^2}{c_i}\end{cases}\]

可以证明,\(c_i\) 在过程中始终是整数,因此我们可以写出一份代码。

# python
from fractions import *
from numpy import *

def solve(D):
    b = 0
    c = 1
    sq = int(sqrt(D))
    if sq * sq == D:
        return []
    lis = [int((sq + b) / c)]
    while lis[-1] != lis[0] * 2:
        b = lis[-1] * c - b
        c = (D - b * b) // c
        lis.append(int((sq + b) / c))
    return lis

def pell_min(D):
    lis = solve(D)
    if len(lis) == 0:
        return 0, 0
    x, y = 0, 1
    lis.pop(-1)
    r = len(lis)
    while len(lis) > 0:
        x, y = y, x + lis[-1] * y
        lis.pop(-1)
    if r & 1:
        return 2 * x * x - 1, 2 * x * y
    else:
        return x, y

ans = 0
maxD = 0
for D in range(1, 1001):
    x, y = pell_min(D)
    if ans < x:
        ans = x
        maxD = D

print(maxD, ans)

67

数字三角形,定义动态规划转移方程:

\[dp[i][j] = \max(dp[i - 1][j - 1], \, dp[i - 1][j]) + a[i][j] \]

取最后一行的最大值即可。

// cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 110;
int a[N][N], dp[N][N];

int main() {
    int n = 100;
    for (int i = 1; i <= n; i ++ ) {
        for (int j = 1; j <= i; j ++ ) {
            cin >> a[i][j];
        }
    }
    for (int i = 1; i <= n; i ++ ) {
        for (int j = 1; j <= i; j ++ ) {
            dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - 1]) + a[i][j];
        }
    }
    int ans = 0;
    for (int i = 1; i <= n; i ++ ) ans = max(ans, dp[n][i]);
    cout << ans;
    return 0;
}

68

因为只有十个数,我们直接对点标号,可以直接遍历所有排列求解。

image

// cpp
#include<bits/stdc++.h>
using namespace std;

string to_c(int t) {
    if (t == 10) return "10";
    string s = "";
    s += char(t + '0');
    return s;
}

string trans(int *a) {
    string s = "";
    for (int i = 0; i < 5; i ++ ) {
        s += to_c(a[i * 2 + 1]);
        s += to_c(a[i * 2]);
        s += to_c(a[(i + 1) * 2 % 10]);
    }
    return s;
}

int main() {
    int a[10];
    iota(a, a + 10, 1);
    string ans = "";
    do {
        if (a[0] + a[1] != a[3] + a[4]) continue;
        if (a[2] + a[3] != a[5] + a[6]) continue;
        if (a[4] + a[5] != a[7] + a[8]) continue;
        if (a[6] + a[7] != a[9] + a[0]) continue;
        if (a[8] + a[9] != a[1] + a[2]) continue;
        if (a[1] > min(a[3], min(a[5], min(a[7], a[9])))) continue;
        string s = trans(a);
        if (s.length() == 16 && s > ans) ans = s;
    } while(next_permutation(a, a + 10));
    cout << ans << "\n";
    return 0;
}

69

筛出欧拉函数即可。

// cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int primes[N], st[N], cnt;
int phi[N];

int main() {
    phi[1] = 1;
    for (int i = 2; i < N; i ++ ) {
        if (!st[i]) primes[cnt ++ ] = i, phi[i] = i - 1;
        for (int j = 0; i * primes[j] < N; j ++ ) {
            st[i * primes[j]] = 1;
            if (i % primes[j] == 0) {
                phi[i * primes[j]] = phi[i] * primes[j];
                break;
            }
            phi[i * primes[j]] = phi[i] * (primes[j] - 1);
        }
    }
    int ans = 0;
    double maxx = 0;
    for (int i = 2; i <= int(1e6); i ++ ) {
        if ((double)i / phi[i] > maxx) {
            maxx = (double)i / phi[i];
            ans = i;
        }
    }
    cout << ans << " " << maxx << "\n";
    return 0;
}

70

同样的方法,筛出之后暴力判断即可,比较好的一个剪枝是先判大小再判合不合法。

// cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 1e7 + 10;
int primes[N], st[N], cnt;
int phi[N];

bool check(int x) {
    string s = to_string(x), t = to_string(phi[x]);
    sort(s.begin(), s.end()), sort(t.begin(), t.end());
    return s == t;
}

int main() {
    phi[1] = 1;
    for (int i = 2; i < N; i ++ ) {
        if (!st[i]) primes[cnt ++ ] = i, phi[i] = i - 1;
        for (int j = 0; i * primes[j] < N; j ++ ) {
            st[i * primes[j]] = 1;
            if (i % primes[j] == 0) {
                phi[i * primes[j]] = phi[i] * primes[j];
                break;
            }
            phi[i * primes[j]] = phi[i] * (primes[j] - 1);
        }
    }
    int ans = 0;
    double maxx = 1e9;
    for (int i = 2; i < int(1e7); i ++ ) {
        if ((double)i / phi[i] < maxx && check(i)) {
            maxx = (double)i / phi[i];
            ans = i;
        }
    }
    cout << ans << " " << maxx << "\n";
    return 0;
}
posted @ 2025-04-29 10:57  YipChip  阅读(30)  评论(0)    收藏  举报