Hướng dẫn giải của [VTT Q4 Contest 15] Bài 4: Điền số [CELLFILL]
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