**Write-up** [Project Euler](https://projecteuler.net) --- **Problem 169: Sums of Powers of Two** - 99,99999% --- * Đề [Problem 169](https://projecteuler.net/problem=169) ![](https://hackmd.io/_uploads/r1Kl8vmfT.png) * Sau khi chạy trâu các số từ 1 -> 20 và thử submit lên [oeis.org](https://oeis.org/) thì được kết quả là dãy [Stern's Diatomic Series](https://mathworld.wolfram.com/SternsDiatomicSeries.html). Do đó có công thức truy hồi số cách biểu diễn của số n là: * $a_{n}$ = $\begin{cases} a_{n / 2} \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ n \ chẵn \\ a_{(n + 1) / 2} + a_{(n - 1) / 2} \ \ \ \ \ n \ lẻ \end{cases}$ * Tuy nhiên vì dãy Stern's Diatomic Series bắt đầu đếm từ 0, còn dãy ta cần tính bắt đầu từ 1 nên khi tính, để ra kết quả chính xác thì ta tính $n+1$ sẽ được kết quả của số $n$ * Nhìn vào có thể hoàn toàn cài đặt bằng đệ quy, tuy nhiên số cần tính là 10^25^, vượt quá kiểu long long trong C++ nhưng vẫn có thể tính toán trong Python 1 cách bình thường ```python num = {0: 1, 1: 1} def cal(n): if n in num: return num[n] if n % 2 == 1: num[n] = cal((n + 1) // 2) + cal((n - 1) // 2) return num[n] else: num[n] = cal(n // 2) return num[n] print(cal(10 ** 25 + 1)) ``` => 178653872807 **Problem 185: Number Mind** - 100% --- * Đề: [Problem 185](https://projecteuler.net/problem=185) ![](https://hackmd.io/_uploads/ryAvHHGMa.png) * Đề chỉ yêu cầu đơn giản là tìm ra số n có 16 số thỏa các điều kiện các chữ số đúng với các số cho sẵn ban đầu * Vì số có 16 chữ số là 10^16, nếu sinh 10^16 số và duyệt thì đợi rất lâu, vậy nên ý tưởng duyệt trâu từng điều kiện, giả sử ở số s hiện tại k số đúng là s~1~, s~2~,.. s~k~ thì ở các số tiếp theo sẽ cài đặt sao cho n - k số còn lại đều không đúng * Thử chạy trâu khi chưa xác định được độ phức tạp thì vô tình có kết quả trong khoảng 10s * Hoàn toàn cài theo yêu cầu của đề, k số đúng thì n - k số còn lại không đúng, nếu những số đã đúng trùng với số đang xét thì số đang xét cũng đúng - hoàn toàn quay lui không có gì thêm ```cpp #include<bits/stdc++.h> using namespace std; vector<pair<int, string>> num; string b; int vis[20][10], a; int patch[20]; int n_patch[20]; bool ret = false; bool check() { for(int i = 0; i < 16; i++) { if(!patch[i]) continue; if(vis[i][n_patch[i]]) return false; } return true; } bool f_ans() { for(int i = 0; i < 16; i++) { if(patch[i] && vis[i][n_patch[i]]) return false; bool tmp = false; for(int j = 0 + (i == 0); j < 10; j++) tmp |= !(vis[i][j]); if(!tmp) return false; } for(int i = 0; i < 16; i++) { if(patch[i]) cout << n_patch[i]; else for(int j = 0 + (i == 0); j < 10; j++) if(!vis[i][j]) { cout << j; break; } } return true; } void solve(int k) { if (ret) return; if(k == num.size()) { ret = f_ans(); return; } string tmp = num[k].second; int u = num[k].first; if(u == 0) { for(int i = 0; i < tmp.size(); i++) vis[i][tmp[i] - '0']++; solve(k + 1); return; } if(u == 1) { for(int i = 0; i < tmp.size(); i++) { if(ret) return; if(vis[i][tmp[i] - '0']) continue; if(patch[i] && n_patch[i] != (tmp[i] - '0')) continue; patch[i]++; n_patch[i] = tmp[i] - '0'; bool atmp = true; for(int j = 0; j < tmp.size(); j++) { if(i == j) continue; if(patch[j] && n_patch[j] == (tmp[j] - '0')) { atmp = false; break; } } if(atmp) { for(int j = 0; j < tmp.size(); j++) { if(j == i) continue; vis[j][tmp[j] - '0']++; } solve(k + 1); for(int j = 0; j < tmp.size(); j++) { if(j == i) continue; vis[j][tmp[j] - '0']--; } } patch[i]--; } return; } if(u == 2) { for(int i = 0; i < tmp.size(); i++) for(int x = i + 1; x < tmp.size(); x++) { if(ret) return; if(vis[i][tmp[i] - '0'] || vis[x][tmp[x] - '0']) continue; if((patch[i] && n_patch[i] != (tmp[i] - '0')) || (patch[x] && n_patch[x] != (tmp[x] - '0'))) continue; patch[i]++; patch[x]++; n_patch[i] = tmp[i] - '0'; n_patch[x] = tmp[x] - '0'; bool atmp = true; for(int j = 0; j < tmp.size(); j++) { if(i == j) continue; if(x == j) continue; if(patch[j] && n_patch[j] == (tmp[j] - '0')) { atmp = false; break; } } if(atmp) { for(int j = 0; j < tmp.size(); j++) { if(j == i) continue; if(j == x) continue; vis[j][tmp[j] - '0']++; } solve(k + 1); for(int j = 0; j < tmp.size(); j++) { if(j == i) continue; if(j == x) continue; vis[j][tmp[j] - '0']--; } } patch[i]--; patch[x]--; } return; } if(u == 3) { for(int i = 0; i < tmp.size(); i++) for(int x = i + 1; x < tmp.size(); x++) for(int y = x + 1; y < tmp.size(); y++) { if(ret) return; if(vis[i][tmp[i] - '0'] || vis[x][tmp[x] - '0'] || vis[y][tmp[y] - '0']) continue; if((patch[i] && n_patch[i] != (tmp[i] - '0')) || (patch[x] && n_patch[x] != (tmp[x] - '0')) || (patch[y] && n_patch[y] != (tmp[y] - '0'))) continue; patch[i]++; patch[x]++; patch[y]++; n_patch[i] = tmp[i] - '0'; n_patch[x] = tmp[x] - '0'; n_patch[y] = tmp[y] - '0'; bool atmp = true; for(int j = 0; j < tmp.size(); j++) { if(i == j) continue; if(x == j) continue; if(y == j) continue; if(patch[j] && n_patch[j] == (tmp[j] - '0')) { atmp = false; break; } } if(atmp) { for(int j = 0; j < tmp.size(); j++) { if(j == i) continue; if(j == x) continue; if(j == y) continue; vis[j][tmp[j] - '0']++; } solve(k + 1); for(int j = 0; j < tmp.size(); j++) { if(j == i) continue; if(j == x) continue; if(j == y) continue; vis[j][tmp[j] - '0']--; } } patch[i]--; patch[x]--; patch[y]--; } return; } } int main() { ios_base::sync_with_stdio(false); cin.tie(0); freopen("text.txt", "r", stdin); while(cin >> a >> b) num.push_back({a, b}); sort(num.begin(), num.end()); solve(0); } ``` ![](https://hackmd.io/_uploads/BJbU8HzMT.png) **Problem 231 : Prime factorisation of binomial coefficients** - 100% --- * Đề: [Problem 231](https://projecteuler.net/problem=231) ![](https://hackmd.io/_uploads/SyihKpo-T.png) - Phân tích tổ hợp chập nCk thành tích các thừa số nguyên tố có dạng 2^a^x3^b^x5^c^..., sau đó tính tổng của 2a+3b+5c+... * Vì trong công thức tính nCk là các giai thừa nên nó sẽ là tích tất cả các số <= n, vậy nên toàn bộ ước nguyên tố lẫn lũy thừa của ước nguyên tố đều sẽ được tính * Do đó có thể sử dụng phân tích n! thành tích nguyên tố để đếm trong độ phức tạp O(log~e~n), dùng sàng nguyên tố để prebuild các số nguyên tố trong khoảng từ 2 -> n sau đó tính toán để giảm độ phức tạp * Hoàn toàn có thể chạy trong thời gian nhỏ vì độ phức tạp không quá lớn: O(nlog~e~n) với n = 2e7 ```cpp #include<bits/stdc++.h> using namespace std; #define N (long long)2e7 #define M (long long)15e6 bool prime[N + 5]; vector <long long> num; void build() { for(int i = 2; i * i <= N; i++) { if(prime[i]) continue; for(int j = i * i; j <= N; j += i) prime[j] = true; } for(int i = 2; i <= N; i++) if(!prime[i]) num.push_back(i); } long long cal(long long k) { long long ret = 0; long long c_p, tmp; for(int i = 0; i < num.size(); i++) { tmp = num[i]; while(tmp <= k) { ret += num[i] * (k / tmp); c_p++; tmp *= num[i]; } } return ret; } long long n, m; int main() { ios_base::sync_with_stdio(false); cin.tie(0); build(); cout << cal(N) - (cal(M) + cal(N - M)); } ``` ![](https://hackmd.io/_uploads/SJadXfMGT.png) **Problem 237: Tours on a 4xN Playing Board** - 100% --- * Đề: [Problem 237](https://projecteuler.net/problem=237) ![](https://hackmd.io/_uploads/ryOFUffM6.png) * Biết được bài toán có dạng quy hoạch động nên tìm công thức quy hoạch động bằng cách: * Chạy trâu quay lui tìm đường đi trên bảng, sinh 10 giá trị đầu của T(n) ```cpp #include <bits/stdc++.h> using namespace std; #define N 20 int num[5][N + 5]; int ans; bool vis[5][N + 5]; int x[5]{0, 0, 1, -1}; int y[5]{1, -1, 0, 0}; void atmp(int n, int i, int j, int s) { if(s == n * 4 - 1 && i == 4 && j == 1) { ans++; return; } for(int k = 0; k < 4; k++) { int u = i + x[k]; int v = j + y[k]; if(u < 1 || u > 4) continue; if(v < 1 || v > n) continue; if(vis[u][v]) continue; vis[u][v] = true; atmp(n, u, v, s + 1); vis[u][v] = false; } } int main() { vis[1][1] = true; for(int i = 1; i <= 10; i++) { ans = 0; atmp(i, 1, 1, 0); cout << ans << " "; } return 0; } ``` ![](https://hackmd.io/_uploads/S1Yt78GMp.png) * => Sau đó đưa dãy số vào [oeis.org](https://oeis.org/ ) tìm được công thức truy hồi: * $a_{n}$ = $a_{n-4}$ - $2a_{n-3}$ + $2a_{n-2}$ + $2a_{n-1}$ * Tuy nhiên n lớn (10^12^) nên quy đổi bài toán sang nhân ma trận để tính lũy thừa trong độ phức tạp log, giảm thời gian chờ kết quả: * Ma trận ban đầu $\begin{bmatrix} a_{1} \ \ \ a_{2} \ \ \ a_{3} \ \ \ a_{4}\end{bmatrix}$ = $\begin{bmatrix} 1 \ \ \ 1 \ \ \ 4 \ \ \ 8 \end{bmatrix}$ * Ma trận cơ sở: $\begin{bmatrix} 0 \ \ \ 0 \ \ \ 0 \ \ \ \ 1 \\ 1 \ \ \ 0 \ \ \ 0 \ {-2} \\ 0 \ \ \ 1 \ \ \ 0 \ \ \ 2 \\ 0 \ \ \ 0 \ \ \ 1 \ \ \ 2 \end{bmatrix}$ * Phép nhân ma trận: $\begin{bmatrix} a_{n - 3} \ \ \ a_{n - 2} \ \ \ a_{n - 1} \ \ \ a_{n}\end{bmatrix}$ * $\begin{bmatrix} 0 \ \ \ 0 \ \ \ 0 \ \ \ \ 1 \\ 1 \ \ \ 0 \ \ \ 0 \ {-2} \\ 0 \ \ \ 1 \ \ \ 0 \ \ \ 2 \\ 0 \ \ \ 0 \ \ \ 1 \ \ \ 2 \end{bmatrix}$ = $\begin{bmatrix} a_{n - 2} \ \ \ a_{n - 1} \ \ \ a_{n} \ \ \ a_{n + 1}\end{bmatrix}$ * => $\begin{bmatrix} a_{n - 3} \ \ \ a_{n - 2} \ \ \ a_{n - 1} \ \ \ a_{n}\end{bmatrix}$ * $\begin{bmatrix} 0 \ \ \ 0 \ \ \ 0 \ \ \ \ 1 \\ 1 \ \ \ 0 \ \ \ 0 \ {-2} \\ 0 \ \ \ 1 \ \ \ 0 \ \ \ 2 \\ 0 \ \ \ 0 \ \ \ 1 \ \ \ 2 \end{bmatrix} ^k$ = $\begin{bmatrix} a_{n - 3 + k} \ \ \ a_{n - 2 + k} \ \ \ a_{n - 1 + k} \ \ \ a_{n + k}\end{bmatrix}$ Kết quả cuối cùng sẽ là lấy ma trận ban đầu nhân cột 4 của ma trận $\begin{bmatrix} 0 \ \ \ 0 \ \ \ 0 \ \ \ \ 1 \\ 1 \ \ \ 0 \ \ \ 0 \ {-2} \\ 0 \ \ \ 1 \ \ \ 0 \ \ \ 2 \\ 0 \ \ \ 0 \ \ \ 1 \ \ \ 2 \end{bmatrix} ^{10^{12}-4}$ * Độ phức tạp nhỏ: nhân ma trận O(4x4x4) x lũy thừa tốn O(log~2~(N)) (N = 10^12^) = tổng thể độ phức tạp sẽ là O(12xlog~2~(N)) ```cpp #include <bits/stdc++.h> using namespace std; #define N (long long)1e12 #define M (long long)1e8 long long a[5]{0, 1, 1, 4, 8}; struct mat{ long long num[5][5]{{},{},{},{},{}}; }; mat Base; mat mul(mat a, mat b) { mat ret; for(int i = 1; i <= 4; i++) for(int j = 1; j <= 4; j++) for(int k = 1; k <= 4; k++) { ret.num[i][j] += a.num[i][k] * b.num[k][j]; ret.num[i][j] %= M; } return ret; } mat m_pow(mat a, long long b) { if(b == 1) return a; mat c = m_pow(a, b / 2); mat k = mul(c, c); if ((b % 2)) k = mul(k, a); return k; } long long ans() { long long ret = 0; for(int i = 1; i <= 4; i++) { ret += a[i] * Base.num[i][4]; ret %= M; } return ret % M; } int main() { Base.num[1][4] = Base.num[2][1] = Base.num[3][2] = Base.num[4][3] = 1; Base.num[3][4] = Base.num[4][4] = 2; Base.num[2][4] = -2; Base = m_pow(Base, N - 4); cout << ans(); } ``` ![](https://hackmd.io/_uploads/H1X_V7fM6.png) **Problem 247: Squares Under a Hyperbola** --- * Đề: [Prblem 247](https://projecteuler.net/problem=247) ![](https://hackmd.io/_uploads/SkRiUvQMT.png)