Hướng dẫn giải của [VTT Q4 Contest 15] Bài 4: Điền số [CELLFILL]


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

Cần ghi các giá trị ~a_i~ lên băng giấy ban đầu toàn 0 với ít lệnh Fill nhất. Lệnh sau chỉ ghi đè được lên các ô nhỏ hơn, nên ta phải ghi từ giá trị nhỏ đến lớn, đoạn to trước, đoạn con sau.

Sub 1: Chia đoạn theo giá trị nhỏ nhất — ~O(n^2)~, dùng khi ~T \le 10, n \le 1000~

Xét đoạn đang nằm trên "nền" có giá trị ~base~. Gọi ~mn~ là giá trị nhỏ nhất của đoạn. Nếu ~mn > base~ thì cần đúng một lệnh phủ cả đoạn bằng ~mn~ (không thể rẻ hơn vì các ô nhỏ nhất phải được ghi bằng một lệnh nào đó phủ chúng). Sau đó các vị trí có giá trị ~mn~ chia đoạn thành các đoạn con, mỗi đoạn con xử lý đệ quy với nền ~mn~. Nếu chỉ nộp sub này: đúng 100/100 test của bộ test hiện có (do các test lớn vẫn nhỏ về ~n~ ở nhóm sub này).

Sub 2: Stack đơn điệu — ~O(n)~

Cách làm trên chính là đếm số "giá trị dương mới" khi duyệt bằng stack tăng, ban đầu stack chứa số 0. Với mỗi ~a_i~, bỏ các phần tử lớn hơn ~a_i~ khỏi đỉnh; nếu đỉnh nhỏ hơn ~a_i~ thì đẩy ~a_i~ vào và tăng đáp án thêm 1; nếu đỉnh bằng ~a_i~ thì ô này cùng một lệnh với ô trước đó. Mỗi phần tử vào ra stack một lần. Nếu chỉ nộp sub này: đúng 100/100 test.

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

main đọc tất cả bộ test; nếu ~T \le 10~ và mọi ~n \le 1000~ thì gọi sub1, ngược lại gọi sub2. 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;

int T;
vector<vector<int>> tests;

// Đoạn [l, r] đang nằm trên nền có giá trị base. Giá trị nhỏ nhất của đoạn là mn.
// Nếu mn > base thì cần 1 lệnh Fill phủ cả đoạn bằng mn, rồi chia đoạn theo các vị trí bằng mn và làm tiếp trên nền mn.
int solve(const vector<int>& a, int l, int r, int base) {
    if (l > r) return 0;
    int mn = *min_element(a.begin() + l, a.begin() + r + 1);
    int res = (mn > base) ? 1 : 0;
    int start = l;
    for (int i = l; i <= r + 1; i++)
        if (i == r + 1 || a[i] == mn) {
            res += solve(a, start, i - 1, mn);
            start = i + 1;
        }
    return res;
}

// Sub 1: chia đoạn theo giá trị nhỏ nhất, ~O(n^2)~ mỗi bộ test, dùng khi T <= 10, n <= 1000
void sub1() {
    for (auto& a : tests) cout << solve(a, 0, (int)a.size() - 1, 0) << "\n";
}

// Sub 2: dùng stack đơn điệu tăng. Mỗi lần xuất hiện một giá trị dương mới mà không "nối" được
// với phần tử bằng nó đang nằm trong stack thì cần thêm một lệnh. ~O(n)~
void sub2() {
    for (auto& a : tests) {
        vector<int> st;
        st.push_back(0);                       // nền ban đầu toàn 0
        int cnt = 0;
        for (int x : a) {
            while (st.back() > x) st.pop_back();
            if (st.back() < x) { st.push_back(x); cnt++; }
        }
        cout << cnt << "\n";
    }
}

int main() {
    freopen("CELLFILL.INP", "r", stdin);
    freopen("CELLFILL.OUT", "w", stdout);
    ios::sync_with_stdio(false); cin.tie(nullptr);
    cin >> T;
    tests.resize(T);
    size_t mx = 0;
    for (int t = 0; t < T; t++) {
        int n; cin >> n;
        tests[t].resize(n);
        for (int& x : tests[t]) cin >> x;
        mx = max(mx, tests[t].size());
    }
    if (T <= 10 && mx <= 1000) sub1();
    else sub2();
}

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.