Search for a command to run...
Progressive hints first, then the full explanation and implementation when you're ready to cash out.
Review status
AI-generated and still unreviewed. Double-check the details before internalizing them.
Hints
Open only as much as you need to keep the solve alive.
For one blog, repeated mentions before the last mention of the same user are irrelevant. Its visible contribution is the original list read backwards while keeping each user once.
For any fixed posting order, final is all distinct users sorted by the time of their last mention, most recent first. So inspect the chosen blogs in reverse posting order.
In reverse order, choosing a blog appends its currently unseen reversed-deduplicated block to the answer, then deletes those users from every remaining blog.
The blocks you compare are not static. After outputting users, every remaining blog shrinks; sorting once is the trap.
At each reverse step, greedily take the lexicographically smallest current block. If it is a prefix of another block, taking it first can only expose the suffix later, so it cannot hurt.
Research note: I checked the official Codeforces editorial and the problem page. The official intended solution is the same reverse greedy idea; the supplied statement is the canonical version here.
The operation looks like a deque simulation problem, but the actual invariant is simpler: after all mentions are processed, is ordered by decreasing time of last mention. The first element is the user mentioned most recently, the second is the user whose last mention is next most recent, and so on. Earlier mentions of the same user are dead weight.
So for one blog , if we view it from the end to the beginning and keep each user only the first time we see it, we get its effective reverse block . Example: becomes . That is exactly what this blog can contribute to the final when we process blogs in reverse posting order.
Now choose the blogs in reverse. Maintain a set of users already placed into the answer. For every unchosen blog, keep its current block after deleting users from . If we choose blog now, we append this current block to the answer, add its users to , and delete those users from every remaining block.
The greedy step is: choose the lexicographically smallest current block. Do not sort once and call it a day; after every chosen block, the other blocks shrink. That mutation is the whole problem, because of course the blogs had to be annoying.
Why the greedy is safe:
Let be the lexicographically smallest current block, and compare it with any other possible first choice .
If and first differ at some position and has the smaller value there, then every answer starting with is immediately lexicographically smaller than every answer starting with .
The only subtle case is when is a prefix of . Write . If we take first, then all values of are deleted from , so becomes exactly because every block has distinct values. Then we can choose this shortened next and reproduce the same prefix that choosing first would have produced. The optimal continuation after taking can only be better. Empty blocks are just the same prefix argument with .
Therefore there is always an optimal solution whose next reverse blog is the smallest current block. Repeating by induction gives the globally lexicographically smallest .
Implementation details:
Let . Preprocessing is . Each round compares all remaining blocks and filters at most the total current length, so the total is , easily fine for . Memory is for the blocks and timestamp arrays.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
int main() {
setIO();
const int MAXA = 1000000;
vector<int> seen(MAXA + 1, 0), used(MAXA + 1, 0);
int seenTag = 1, usedTag = 1;
int T;
cin >> T;
while (T--) {
++usedTag;
int n;
cin >> n;
vector<vector<int>> blogs(n);
for (int i = 0; i < n; i++) {
int l;
cin >> l;
vector<int> raw(l);
for (int &x : raw) cin >> x;
++seenTag;
for (int j = l - 1; j >= 0; j--) {
int x = raw[j];
if (seen[x] != seenTag) {
seen[x] = seenTag;
blogs[i].push_back(x);
}
}
}
auto smaller = [](const vector<int> &a, const vector<int> &b) {
return lexicographical_compare(a.begin(), a.end(), b.begin(), b.end());
};
vector<char> done(n, 0);
vector<int> ans;
for (int step = 0; step < n; step++) {
int best = -1;
for (int i = 0; i < n; i++) {
if (!done[i] && (best == -1 || smaller(blogs[i], blogs[best]))) {
best = i;
}
}
bool added = false;
for (int x : blogs[best]) {
ans.push_back(x);
used[x] = usedTag;
added = true;
}
done[best] = 1;
if (added) {
for (int i = 0; i < n; i++) {
if (done[i]) continue;
vector<int> filtered;
filtered.reserve(blogs[i].size());
for (int x : blogs[i]) {
if (used[x] != usedTag) filtered.push_back(x);
}
blogs[i].swap(filtered);
}
}
}
for (int i = 0; i < (int)ans.size(); i++) {
if (i) cout << ' ';
cout << ans[i];
}
cout << '\n';
}
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
int main() {
setIO();
const int MAXA = 1000000;
vector<int> seen(MAXA + 1, 0), used(MAXA + 1, 0);
int seenTag = 1, usedTag = 1;
int T;
cin >> T;
while (T--) {
++usedTag;
int n;
cin >> n;
vector<vector<int>> blogs(n);
for (int i = 0; i < n; i++) {
int l;
cin >> l;
vector<int> raw(l);
for (int &x : raw) cin >> x;
++seenTag;
for (int j = l - 1; j >= 0; j--) {
int x = raw[j];
if (seen[x] != seenTag) {
seen[x] = seenTag;
blogs[i].push_back(x);
}
}
}
auto smaller = [](const vector<int> &a, const vector<int> &b) {
return lexicographical_compare(a.begin(), a.end(), b.begin(), b.end());
};
vector<char> done(n, 0);
vector<int> ans;
for (int step = 0; step < n; step++) {
int best = -1;
for (int i = 0; i < n; i++) {
if (!done[i] && (best == -1 || smaller(blogs[i], blogs[best]))) {
best = i;
}
}
bool added = false;
for (int x : blogs[best]) {
ans.push_back(x);
used[x] = usedTag;
added = true;
}
done[best] = 1;
if (added) {
for (int i = 0; i < n; i++) {
if (done[i]) continue;
vector<int> filtered;
filtered.reserve(blogs[i].size());
for (int x : blogs[i]) {
if (used[x] != usedTag) filtered.push_back(x);
}
blogs[i].swap(filtered);
}
}
}
for (int i = 0; i < (int)ans.size(); i++) {
if (i) cout << ' ';
cout << ans[i];
}
cout << '\n';
}
}