Hướng dẫn giải của Tìm số_LS


Chỉ dùng lời giải này khi không có ý tưởng, và đừng copy-paste code từ lời giải này. Hãy tôn trọng người ra đề và người viết lời giải.
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ập

Tác giả: Hiếu Nguyễn, thaituandz345, Dinn, oqtn75

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:

  1. 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'.
  2. 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_len và vòng lặp while.

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.

  1. Đọc dữ liệu vào a, n, k.
  2. Chuyển a thành chuỗi s để lấy độ dài và các ký tự.
  3. Tính chỉ số: (k - 1) % s.size(). Ta trừ 1 vì chỉ số mảng bắt đầu từ 0, còn k là số đếm từ 1.
  4. 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_nlen1 * 2^{n-1}.
  • Nếu k nằm trong nửa đầu của S_n, nó tương ứng với S_{n-1}.
  • Nếu k nằm trong nửa sau, nó tương ứng với một vị trí trong S_{n-1} (vị trí đó là k trừ đi độ dài của nửa đầu).

Thuật toán:

  1. Tính độ dài chuỗi ban đầu len1.
  2. Dùng vòng lặp while để giảm n về 1.
  3. Trong mỗi bước, tính half (độ dài S_{n-1}). Nếu k > half, cập nhật k = k - half.
  4. Khi n = 1, chuỗi chỉ còn a, ta in ra a[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 n nhỏ 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 k và độ dài chuỗi a (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

Please read the guidelines before commenting.


Không có bình luận tại thời điểm này.