Hướng dẫn giải của [Beginner Free Contest 09] DCTNAME


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.

Hướng dẫn giải

Tìm xâu con chung (không nhất thiết liên tiếp) của hai xâu ~s_1, s_2~ có thứ tự từ điển lớn nhất. Lưu ý theo đề, xâu dài hơn mà có xâu kia làm tiền tố thì lớn hơn, nên luôn nên lấy thêm ký tự khi còn có thể.

Sub 1: Quy hoạch động trên xâu — dùng khi độ dài ~\le 100~

Gọi ~best[i][j]~ là xâu con chung lớn nhất của ~s_1[i..]~ và ~s_2[j..]~: lấy lớn nhất của ~best[i+1][j]~, ~best[i][j+1]~, và nếu ~s_1[i] = s_2[j]~ thì thêm ký tự đó vào đầu ~best[i+1][j+1]~. Nếu chỉ nộp sub này: đúng 30/100 test.

Sub 2: Tham lam với bảng vị trí kế tiếp — ~O(26 \cdot (n+m))~

Ký tự đầu của đáp án phải là chữ cái lớn nhất có mặt ở cả hai xâu; lấy lần xuất hiện đầu tiên của nó ở mỗi xâu (để chừa nhiều chỗ nhất cho phần sau), rồi lặp lại từ vị trí ngay sau đó, dừng khi không còn chữ cái chung. Dùng bảng ~nxt[i][c]~ = vị trí đầu tiên của chữ ~c~ từ ~i~ trở đi. Nếu chỉ nộp sub này: đúng 100/100 test, nhưng tốn bộ nhớ ~26(n+m)~ số nguyên nên có nguy cơ với độ dài ~10^6~.

Sub 3: Tham lam với danh sách vị trí và con trỏ — ~O(26 \cdot (n+m))~

Cùng ý tưởng sub 2, nhưng mỗi chữ cái chỉ giữ danh sách vị trí ở từng xâu cùng một con trỏ chỉ tiến lên (vì vị trí hiện tại chỉ tăng), bộ nhớ chỉ ~O(n+m)~. Nếu chỉ nộp sub này: đúng 100/100 test.

Chương trình hoàn chỉnh

Main đọc hai xâu (máy chấm dùng đọc/ghi chuẩn); xâu rất ngắn gọi sub1, vừa gọi sub2, dài gọi sub3. Khi đi thi nên viết như vậy: sub sau có sai hoặc chưa kịp làm thì vẫn ăn điểm sub trước.

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

string s, t;

// Sub 1: quy hoạch động trên xâu — best[i][j] = xâu con chung lớn nhất (từ điển) của s[i..], t[j..].
// Dùng khi độ dài <= 100.
void sub1() {
    int n = s.size(), m = t.size();
    vector<vector<string>> best(n + 2, vector<string>(m + 2, ""));
    for (int i = n - 1; i >= 0; i--)
        for (int j = m - 1; j >= 0; j--) {
            string r = max(best[i + 1][j], best[i][j + 1]);
            if (s[i] == t[j]) r = max(r, s[i] + best[i + 1][j + 1]);
            best[i][j] = r;
        }
    printf("%s\n", best[0][0].c_str());
}

// Sub 2: tham lam. Ký tự đầu của đáp án là chữ cái lớn nhất 'c' có mặt ở cả hai xâu (từ vị trí hiện tại);
// lấy lần xuất hiện đầu tiên của c ở mỗi xâu rồi lặp lại sau vị trí đó. Dùng bảng nxt[c][i] = vị trí
// đầu tiên của c từ i trở đi. Bộ nhớ O(26*(n+m)), dùng khi độ dài <= 5000.
void sub2() {
    int n = s.size(), m = t.size();
    vector<array<int, 26>> A(n + 2), B(m + 2);
    A[n].fill(-1); A[n + 1].fill(-1); B[m].fill(-1); B[m + 1].fill(-1);
    for (int i = n - 1; i >= 0; i--) { A[i] = A[i + 1]; A[i][s[i] - 'a'] = i; }
    for (int j = m - 1; j >= 0; j--) { B[j] = B[j + 1]; B[j][t[j] - 'a'] = j; }
    string res;
    int i = 0, j = 0;
    while (i < n && j < m) {
        bool ok = false;
        for (int c = 25; c >= 0; c--)
            if (A[i][c] != -1 && B[j][c] != -1) {
                res += char('a' + c);
                i = A[i][c] + 1; j = B[j][c] + 1;
                ok = true; break;
            }
        if (!ok) break;
    }
    printf("%s\n", res.c_str());
}

// Sub 3: cùng tham lam nhưng không lập bảng lớn: mỗi chữ cái giữ danh sách vị trí ở từng xâu và
// một con trỏ chỉ tiến (vì vị trí hiện tại chỉ tăng). Tổng O(26*(n+m)) thời gian, O(n+m) bộ nhớ.
void sub3() {
    int n = s.size(), m = t.size();
    vector<int> P[26], Q[26];
    for (int i = 0; i < n; i++) P[s[i] - 'a'].push_back(i);
    for (int j = 0; j < m; j++) Q[t[j] - 'a'].push_back(j);
    size_t pp[26] = {0}, qq[26] = {0};
    string res;
    int i = 0, j = 0;
    while (true) {
        bool ok = false;
        for (int c = 25; c >= 0; c--) {
            while (pp[c] < P[c].size() && P[c][pp[c]] < i) pp[c]++;
            while (qq[c] < Q[c].size() && Q[c][qq[c]] < j) qq[c]++;
            if (pp[c] < P[c].size() && qq[c] < Q[c].size()) {
                res += char('a' + c);
                i = P[c][pp[c]] + 1; j = Q[c][qq[c]] + 1;
                ok = true; break;
            }
        }
        if (!ok) break;
    }
    printf("%s\n", res.c_str());
}

int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    cin >> s >> t;
    size_t L = max(s.size(), t.size());
    if (L <= 100) sub1();
    else if (L <= 5000) sub2();
    else sub3();
    return 0;
}

Bình luận

Hãy đọc nội quy trước khi bình luận.


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