Hướng dẫn giải của Tìm số_LS
Nộp một lời giải chính thức trước khi tự giải là một hành động có thể bị ban.
Lời giải này đang bị ẩn cho đến khi bạn chọn mở ra.
Chúng tôi khuyên bạn nên tự thử giải bài trước. Việc mở lời giải có thể làm lộ mất ý tưởng chính trước khi bạn có cơ hội tự giải.
Bạn phải đăng nhập để mở lời giải này.
Đăng nhậpTác giả: , , ,
Hiểu bài toán
Bài toán yêu cầu tìm ký tự thứ k trong một chuỗi được tạo ra bằng cách lặp lại một chuỗi ban đầu a n lần. Tuy nhiên, có một sự khác biệt quan trọng giữa các giải pháp đã được nộp:
- Các giải pháp 2 và 3 (được nộp bởi oqtn75 và thaituandz345) giải quyết bài toán: 'Tìm ký tự thứ k (1-indexed) trong chuỗi kết quả là a được lặp lại n lần'.
- Giải pháp 1 (được nộp bởi Dinn) giải quyết một bài toán khác phức tạp hơn: 'Tìm ký tự thứ k trong chuỗi được sinh ra theo quy luật đệ quy: S1 = a, S{i+1} = Si + S_i'. Chuỗi này có độ dài |a| * 2^{n-1}. Điều này được giải thích qua hàm
safe_lenvà vòng lặpwhile.
Vì các giải pháp 2 và 3 là giống nhau và giải pháp 1 là khác biệt, chúng ta sẽ trình bày cả hai cách tiếp cận. Tuy nhiên, với tên bài toán 'Tìm số_LS' và bối cảnh chung, cách tiếp cận lặp lại đơn giản (Giải pháp 2 & 3) thường là mục tiêu chính của các bài toán cơ bản.
Các cách tiếp cận
Cách Lặp lại đơn giản (Phổ biến nhất)
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
long long a, n, k;
cin >> a >> n >> k;
string s = to_string(a);
int len = s.size();
// Sử dụng công thức chu kỳ để tìm ký tự
// k - 1 chuyển về index 0-based
cout << s[(k - 1) % len];
return 0;
}
- Time Complexity: O(1)
- Space Complexity: O(1)
Giải pháp này dựa trên quan sát rằng chuỗi kết quả chỉ là việc lặp lại chuỗi s (chuỗi đại diện cho số a). Do đó, vị trí của ký tự thứ k phụ thuộc chu kỳ của độ dài chuỗi s.
- Đọc dữ liệu vào
a,n,k. - Chuyển
athành chuỗisđể lấy độ dài và các ký tự. - Tính chỉ số:
(k - 1) % s.size(). Ta trừ 1 vì chỉ số mảng bắt đầu từ 0, cònklà số đếm từ 1. - In ra ký tự tại chỉ số đó.
Lưu ý: Nếu k rất lớn (ví dụ k > 10^{18}), giải pháp này vẫn chạy nhanh vì chỉ sử dụng phép chia lấy dư.
Cách Đệ quy/Đối xứng (Theo giải pháp Dinn)
#include <bits/stdc++.h>
using namespace std;
using int64 = long long;
long long safe_len(long long base, long long n) {
long long L = base;
for (long long i = 1; i < n; i++) {
if (L > (long long)2e18) return (long long)2e18;
L <<= 1;
}
return L;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
string a;
long long n, k;
cin >> a >> n >> k;
long long len1 = a.size();
long long len_n = safe_len(len1, n);
// Duyệt từ trên xuống dưới để tìm vị trí trong chuỗi gốc
while (n > 1) {
long long half = safe_len(len1, n - 1);
if (k > half) {
k -= half; // Nếu k nằm ở nửa sau, trừ đi độ dài nửa đầu
}
n--; // Chuyển sang chuỗi cấp độ thấp hơn
}
cout << a[k - 1];
return 0;
}
- Time Complexity: O(n)
- Space Complexity: O(1)
Giải pháp này giải quyết bài toán nơi chuỗi được sinh ra theo quy tắc: S_n = S_{n-1} + S_{n-1}.
- Độ dài của
S_nlàlen1 * 2^{n-1}. - Nếu
knằm trong nửa đầu củaS_n, nó tương ứng vớiS_{n-1}. - Nếu
knằm trong nửa sau, nó tương ứng với một vị trí trongS_{n-1}(vị trí đó làktrừ đi độ dài của nửa đầu).
Thuật toán:
- Tính độ dài chuỗi ban đầu
len1. - Dùng vòng lặp
whileđể giảmnvề 1. - Trong mỗi bước, tính
half(độ dàiS_{n-1}). Nếuk > half, cập nhậtk = k - half. - Khi
n = 1, chuỗi chỉ còna, ta in raa[k-1].
Hàm safe_len dùng để tránh tràn số (overflow) khi tính 2^n cho n lớn.
Cách Thay thế chuỗi (Simulation - Không khả thi)
// Pseudocode minh hoạ cách làm sai (TLE/MLE)
string s = to_string(a);
for(int i = 2; i <= n; i++) {
s = s + s; // Hoặc s += s;
}
cout << s[k-1];
- Time Complexity: O(|a| * 2^n)
- Space Complexity: O(|a| * 2^n)
Đây là cách tiếp cận trực quan nhất nhưng không thể chạy do giới hạn bộ nhớ và thời gian.
- Ta tạo chuỗi kết quả bằng cách nối chuỗi自身.
- Độ dài chuỗi tăng theo hàm lũy thừa 2. Chỉ cần
nnhỏ như 60 hoặc|a|lớn, bộ nhớ sẽ cạn kiệt (TB o(N)).
Phân tích độ phức tạp
| Cách tiếp cận | Time | Space | Tên | ||||
|---|---|---|---|---|---|---|---|
| 1 | O(1) | O(1) | Lặp lại đơn giản (Phổ biến nhất) | ||||
| 2 | O(n) | O(1) | Đệ quy/Đối xứng (Theo giải pháp Dinn) | ||||
| 3 | O( | a | * 2^n) | O( | a | * 2^n) | Thay thế chuỗi (Simulation - Không khả thi) |
Bài học kinh nghiệm
- Bài toán có thể hiểu theo 2 cách: Lặp lại đơn giản (chu kỳ) hoặc Đệ quy đối xứng.
- Với bài toán lặp lại, kết quả chỉ phụ thuộc vào
kvà độ dài chuỗia(dùng phép chia lấy dư). - Với bài toán đệ quy, ta có thể tìm kiếm từ trên xuống (top-down) thay vì xây dựng chuỗi từ dưới lên (bottom-up) để tiết kiệm thời gian.
- Luôn kiểm tra tràn số Overflow khi làm việc với số mũ hoặc độ dài chuỗi lớn.
Lỗi thường gặp
- Quên xử lý tràn số (Overflow) khi tính toán độ dài chuỗi (2^n).
- Sai lệch chỉ số do không chuyển đổi giữa hệ 1-indexed (đề bài) và 0-indexed (mảng/chuỗi).
- Lầm tưởng giữa bài toán 'lặp lại n lần' và 'nối gấp đôi n lần'.
Bình luận