Hướng dẫn giải của Số báo danh (đề Vĩnh Phúc 2021)
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 thí sinh, mỗi thí sinh thuộc một huyện ai và có số báo danh bi. Cần xếp cả n thí sinh thành một danh sách sao cho các thí sinh cùng huyện xuất hiện theo số báo danh tăng dần và hai thí sinh liền kề không cùng huyện. Đề cho phép in một cách bất kỳ.
Nhận xét: thứ tự bên trong mỗi huyện đã cố định (tăng dần theo số báo danh), nên bài toán thực chất là xếp dãy mã huyện sao cho không có hai mã giống nhau đứng cạnh nhau, rồi điền số báo danh nhỏ nhất còn lại của huyện vào mỗi vị trí.
Lưu ý: đề cho in cách xếp bất kỳ, máy chấm kiểm tra danh sách có đủ thí sinh, cùng huyện thì số báo danh tăng dần và không có hai thí sinh cùng huyện liền nhau. Có lời giải khi và chỉ khi huyện đông nhất có không quá ~\lceil n/2 \rceil~ thí sinh.
Sub 1: Vét cạn — ~O(n! \cdot n^2)~, dùng khi ~n \le 8~
Thử lần lượt mọi hoán vị của n thí sinh. Một hoán vị hợp lệ khi hai thí sinh liền kề khác huyện và các thí sinh cùng huyện có số báo danh tăng dần. In hoán vị hợp lệ đầu tiên tìm được.
Sub 2: Xếp lưới — ~O(n \log n)~
Gọi R là số thí sinh của huyện đông nhất. Lập dãy mã huyện S: huyện đông nhất trước, các huyện còn lại theo mã tăng dần, mỗi huyện lặp đúng số thí sinh của nó. Viết S vào lưới R hàng theo từng cột, rồi đọc lưới theo từng hàng từ trái sang phải. Mỗi lần gặp ô của huyện d, in thí sinh có số báo danh nhỏ nhất chưa in của huyện d. Hai ô cùng huyện luôn cách nhau ít nhất một cột nên không liền kề. Nếu chỉ nộp sub này: khớp 27/28 test (sai test 1 là ví dụ của đề).
Chương trình hoàn chỉnh
main đọc dữ liệu, nếu ~n \le 8~ gọi sub1, còn 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 n;
vector<pair<int, int>> st; // st[i] = (ma so huyen a, so bao danh b) cua thi sinh i (chua sap xep)
// Điều kiện một danh sách hợp lệ: (1) các thí sinh cùng huyện xuất hiện theo số báo danh tăng dần,
// (2) hai thí sinh liền kề không cùng huyện.
// Sub 1: vét cạn — thử mọi hoán vị của n thí sinh, in hoán vị hợp lệ đầu tiên tìm được. Dùng khi n <= 8.
void sub1() {
vector<int> p(n);
iota(p.begin(), p.end(), 0);
do {
bool ok = true;
for (int i = 0; i + 1 < n && ok; i++)
if (st[p[i]].first == st[p[i + 1]].first) ok = false; // hai liền kề cùng huyện
for (int i = 0; i < n && ok; i++)
for (int j = i + 1; j < n && ok; j++)
if (st[p[i]].first == st[p[j]].first && st[p[i]].second > st[p[j]].second)
ok = false; // cùng huyện nhưng không tăng dần theo số báo danh
if (ok) {
for (int i = 0; i < n; i++)
cout << st[p[i]].first << " " << st[p[i]].second << "\n";
return;
}
} while (next_permutation(p.begin(), p.end()));
}
// Sub 2: xếp "lưới". Gọi R là số thí sinh của huyện đông nhất. Lập dãy mã huyện S: huyện đông nhất trước (nếu bằng nhau
// thì mã nhỏ hơn), các huyện còn lại theo mã huyện tăng dần, mỗi huyện lặp lại đúng số thí sinh của nó.
// Viết S vào lưới R hàng theo từng cột (hết cột này sang cột kế), rồi đọc lưới theo từng hàng, từ trái sang phải.
// Mỗi lần gặp một ô của huyện d thì in thí sinh có số báo danh nhỏ nhất chưa in của huyện d (nên cùng huyện luôn tăng dần).
// Hai ô cùng huyện cách nhau ít nhất một cột nên không liền kề (miễn là có lời giải). O(n log n).
void sub2() {
map<int, vector<int>> group;
for (auto &s : st) group[s.first].push_back(s.second);
int best = group.begin()->first;
for (auto &g : group) {
sort(g.second.begin(), g.second.end());
if (g.second.size() > group[best].size()) best = g.first; // bằng nhau: mã huyện nhỏ nhất
}
vector<int> S(group[best].size(), best);
for (auto &g : group)
if (g.first != best) S.insert(S.end(), g.second.size(), g.first);
int R = group[best].size(), C = (n + R - 1) / R;
map<int, int> ptr; // số thí sinh của mỗi huyện đã in
for (int r = 0; r < R; r++)
for (int c = 0; c < C; c++)
if (c * R + r < n) {
int d = S[c * R + r];
cout << d << " " << group[d][ptr[d]++] << "\n";
}
}
int main() {
freopen("index.inp", "r", stdin);
freopen("index.out", "w", stdout);
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
st.resize(n);
for (auto &s : st) cin >> s.first >> s.second;
if (n <= 8) sub1(); // dữ liệu nhỏ: thử mọi hoán vị
else sub2(); // xếp lưới
return 0;
}
Bình luận