Hướng dẫn giải của [2526 KS1 THCS] Bài 4: Máy in


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

Mỗi ký tự của ~S~ cần đúng một lệnh PRINT. Ngoài ra, lệnh MSET đầu tiên bắt buộc, và mỗi khi ký tự cần in khác ký tự trong bộ nhớ ta tốn thêm đúng 1 lệnh: TEMP (bộ nhớ giữ nguyên) hoặc MSET (bộ nhớ đổi thành ký tự đó). Kiểm tra ví dụ KOIKA: 5 PRINT + MSET(K) + 3 TEMP = 9.

Sub 1: Vét cạn — ~O(2^n)~, dùng khi ~|S| < 25~

Duyệt đệ quy từng vị trí: nếu ký tự trùng bộ nhớ thì miễn phí, nếu khác thì thử TEMP hoặc MSET, có cắt nhánh khi chi phí vượt kỷ lục. Nếu chỉ nộp sub này: đúng 22/30 test.

Sub 2: Quy hoạch động — ~O(26 \cdot n)~

Gọi ~dp[m]~ là số lệnh phụ ít nhất khi bộ nhớ đang chứa ký tự ~m~ sau khi in xong một tiền tố. Chuyển trạng thái: ký tự trùng ~m~ thì giữ nguyên; khác thì ~dp[m]+1~ (TEMP); hoặc đổi bộ nhớ thành ký tự hiện tại với giá ~\min dp + 1~ (MSET). Đáp án là ~\min dp + n~. Nếu chỉ nộp sub này: đúng 30/30 test.

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

Hàm main đọc chuỗi; ~|S| < 25~ gọi sub1 (10 test), còn lại gọi sub2 (20 test). 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;
int n;

// Sub 1: vét cạn — dùng khi |S| < 25
// Mỗi ký tự cần in là mỗi một lệnh PRINT (n lệnh). Ngoài ra, nếu ký tự cần in khác ký tự trong bộ nhớ thì
// phải tốn thêm 1 lệnh: TEMP (bộ nhớ giữ nguyên) hoặc MSET (bộ nhớ đổi thành ký tự đó). Lệnh đầu là MSET.
int best;
void dfs(int i, char mem, int cost) {
    if (cost >= best) return;
    if (i == n) { best = cost; return; }
    if (s[i] == mem) dfs(i + 1, mem, cost);
    else {
        dfs(i + 1, mem, cost + 1);     // TEMP(s[i])
        dfs(i + 1, s[i], cost + 1);    // MSET(s[i])
    }
}
void sub1() {
    best = INT_MAX;
    dfs(0, s[0], 1);                   // MSET đầu tiên đặt đúng ký tự đầu
    cout << best + n << "\n";
}

// Sub 2: quy hoạch động trên ký tự đang nằm trong bộ nhớ (26 trạng thái) — O(26 * n)
void sub2() {
    const int INF = 1e9;
    vector<int> dp(26, 1);             // MSET đầu tiên, chọn ký tự bất kỳ: 1 lệnh
    for (int i = 0; i < n; i++) {
        int c = s[i] - 'A';
        vector<int> nd(26, INF);
        int mn = *min_element(dp.begin(), dp.end());
        for (int m = 0; m < 26; m++) {
            if (m == c) nd[m] = min(nd[m], dp[m]);            // trùng bộ nhớ: chỉ PRINT
            else nd[m] = min(nd[m], dp[m] + 1);               // TEMP(c)
        }
        nd[c] = min(nd[c], mn + 1);                           // MSET(c) từ trạng thái bất kỳ
        dp = nd;
    }
    cout << *min_element(dp.begin(), dp.end()) + n << "\n";
}

int main() {
    ios::sync_with_stdio(false); cin.tie(nullptr);
    cin >> s;
    n = s.size();
    if (n < 25) sub1();
    else sub2();
    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.