ICPC 2021 - H. Prehistoric Programs
Each tablet is a parentheses string. We may permute the tablets, then concatenate all strings. We need the final concatenation to be a correct bracket sequence: [leftmargin=*] every prefix must have nonnegative balanc...
Source-first archive entry
This page is built from the copied files in competitive_programming/icpc/2021/H-prehistoric-programs. Edit
competitive_programming/icpc/2021/H-prehistoric-programs/solution.tex to update the written solution and
competitive_programming/icpc/2021/H-prehistoric-programs/solution.cpp to update the implementation.
The website does not replace those files with hand-maintained HTML. It reads the copied source tree during the build and exposes the exact files below.
Problem Statement
Copied statement text kept beside the solution archive for direct reference.
Problem H
Prehistoric Programs
Time limit: 6 seconds
Archaeologists have discovered exciting clay tablets in deep layers of
Alutila Cave. Nobody was able to decipher the script on the tablets, ex-
cept for two symbols that seem to describe nested structures not unlike
opening and closing parentheses in LISP. Could it be that humans wrote
programs thousands of years ago?
Taken together, the tablets appear to describe a great piece of work –
perhaps a program, or an epic, or even tax records! Unsurprisingly, af-
ter such a long time, the tablets are in a state of disorder. Your job is
to arrange them into a sequence so that the resulting work has a prop-
erly nested parenthesis structure. Considering only opening and closing
parentheses, a properly nested structure is either
• (), or
• (A), where A is a properly nested structure, or
Clay tablet with undeciphered script
• AB, where A and B are properly nested structures. Source: Wikimedia Commons
Input
The first line of input contains one integer n (1 ≤ n ≤ 106 ), the number of tablets. Each of the remaining
n lines describes a tablet, and contains a non-empty string of opening and closing parentheses; symbols
unrelated to the nesting structure are omitted. The strings are numbered from 1 to n in the order that
they appear in the input. The input contains at most 107 parentheses.
Output
Output a permutation of the numbers from 1 to n such that concatenating the strings in this order re-
sults in a properly nested structure. If this happens for multiple permutations, any one of them will be
accepted. If there is no such permutation, output impossible instead.
Sample Input 1 Sample Output 1
2 2
())())() 1
((()
Sample Input 2 Sample Output 2
5 1
( 5
)) 3
(( 2
)) 4
(
Sample Input 3 Sample Output 3
2 impossible
((
)
Editorial
Rendered from the copied solution.tex file. The original TeX source remains
available below.
Per-Tablet Statistics
For one string, define:
$\text{bal}$: total balance, where
(is $+1$ and)is $-1$;$\text{minPref}$: the minimum prefix balance inside that string.
If the current balance before appending this string is $B$, then the string is safe to append exactly when \[ B + \text{minPref} \ge 0. \] After appending it, the new balance becomes \[ B + \text{bal}. \]
Split into Two Groups
Strings with nonnegative total balance are helpful: once we append them, the available balance never gets smaller in the long run. Strings with negative total balance are dangerous and should be postponed.
So we split all tablets into:
positive group: $\text{bal} \ge 0$;
negative group: $\text{bal} < 0$.
If a valid order exists at all, there is one where all positive-group strings come first and all negative-group strings come last.
How to Sort the Positive Group
Inside the positive group, we want the least demanding strings first: the ones that require the smallest starting balance. That means sorting by \[ \text{minPref} \] in decreasing order.
Then we greedily append them in that order, checking \[ B + \text{minPref} \ge 0. \]
How to Sort the Negative Group
For negative-balance strings, the right measure is not just $\text{minPref}$, because after appending the string we lose some balance permanently.
The standard way to view this group is to reverse each string and swap open/close parentheses. This turns it into a positive-balance problem. Under that transformation, the relevant key becomes \[ \text{bal} - \text{minPref}. \]
Therefore the negative group should be sorted by \[ \text{bal} - \text{minPref} \] in decreasing order, and then greedily appended in that order.
Algorithm
For each string, compute $\text{bal}$ and $\text{minPref}$.
If the total balance over all strings is not zero, output
impossible.Split strings into the positive and negative groups.
Sort:
positive group by decreasing $\text{minPref}$;
negative group by decreasing $\text{bal} - \text{minPref}$.
Traverse first the sorted positive group, then the sorted negative group.
Maintain the current balance $B$. If some string has \[ B + \text{minPref} < 0, \] output
impossible.Otherwise append it, update \[ B \leftarrow B + \text{bal}, \] and record the index. enumerate
Correctness Proof
We prove that the algorithm outputs a valid permutation exactly when one exists.
Lemma 1.
If a string with statistics $(\text{bal}, \text{minPref})$ is appended when the current balance is $B$, then the concatenation remains prefix-valid if and only if \[ B + \text{minPref} \ge 0. \]
Proof.
Inside the string, every prefix changes the balance by exactly the corresponding prefix balance of that string. The worst such drop is $\text{minPref}$. So all resulting prefixes stay nonnegative exactly when the minimum possible one, namely $B+\text{minPref}$, is nonnegative. □
Lemma 2.
If a valid ordering exists, then there also exists a valid ordering in which every string with $\text{bal} \ge 0$ appears before every string with $\text{bal} < 0$.
Proof.
A nonnegative-balance string never reduces the total available balance after it is appended, while a negative-balance string does. Swapping a negative string that appears before a nonnegative string cannot make the future balance smaller at the point where the nonnegative string starts. Repeating this exchange process pushes all nonnegative strings to the front without destroying validity. □
Lemma 3.
Among strings with $\text{bal} \ge 0$, sorting by decreasing $\text{minPref}$ is optimal for greedy construction.
Proof.
Take two such strings $A$ and $B$ with $\text{minPref}(A) \ge \text{minPref}(B)$. If some current balance $X$ can append $B$ first, then \[ X + \text{minPref}(B) \ge 0. \] Since $\text{minPref}(A)$ is at least as large, $A$ is also appendable from $X$. After appending $A$, the balance does not decrease overall because $\text{bal}(A)\ge0$, so whatever was possible after $B$ remains possible after $A$. Thus placing the larger $\text{minPref}$ first is never worse. □
Lemma 4.
Among strings with $\text{bal} < 0$, sorting by decreasing $\text{bal} - \text{minPref}$ is optimal.
Proof.
Reverse each negative string and swap every parenthesis. This transforms it into a string with nonnegative total balance. Under this transformation, the role of $\text{minPref}$ becomes exactly \[ \text{bal} - \text{minPref}. \] So by Lemma 3, the transformed strings should be sorted by decreasing that quantity. Reversing the transformation gives exactly the claimed order for the original negative strings. □
Theorem.
The algorithm outputs a valid permutation if and only if one exists.
Proof.
By Lemma 2, it is enough to consider orders with all positive strings first and all negative strings last. By Lemma 3, the chosen order is optimal for the positive group, and by Lemma 4 it is optimal for the negative group. During the final scan, Lemma 1 exactly characterizes whether each next string can be appended safely.
Therefore, if the algorithm fails, no valid ordering exists. If it succeeds, every prefix stays nonnegative and the total balance is zero, so the produced order is a correct bracket sequence. □
Complexity Analysis
Let $L$ be the total number of parentheses in the input. Computing all statistics takes $O(L)$ time. Sorting the tablets takes $O(n \log n)$ time. The memory usage is $O(n)$.
Implementation Notes
We never need to store the full strings after computing $(\text{bal}, \text{minPref})$.
The final total balance must be zero; otherwise a correct bracket sequence is impossible no matter how we permute the tablets.
Code
Exact copied C++ implementation from solution.cpp.
#include <bits/stdc++.h>
using namespace std;
namespace {
struct Tablet {
int index;
int balance;
int min_prefix;
};
bool appendable(int current_balance, const Tablet& tablet) {
return current_balance + tablet.min_prefix >= 0;
}
void solve() {
int n;
cin >> n;
vector<Tablet> positive;
vector<Tablet> negative;
positive.reserve(n);
negative.reserve(n);
long long total_balance = 0;
for (int i = 1; i <= n; ++i) {
string s;
cin >> s;
int balance = 0;
int min_prefix = 0;
for (char ch : s) {
balance += (ch == '(' ? 1 : -1);
min_prefix = min(min_prefix, balance);
}
total_balance += balance;
Tablet tablet = {i, balance, min_prefix};
if (balance >= 0) {
positive.push_back(tablet);
} else {
negative.push_back(tablet);
}
}
if (total_balance != 0) {
cout << "impossible\n";
return;
}
sort(positive.begin(), positive.end(), [](const Tablet& lhs, const Tablet& rhs) {
return lhs.min_prefix > rhs.min_prefix;
});
sort(negative.begin(), negative.end(), [](const Tablet& lhs, const Tablet& rhs) {
return lhs.balance - lhs.min_prefix > rhs.balance - rhs.min_prefix;
});
vector<int> order;
order.reserve(n);
int current_balance = 0;
for (const Tablet& tablet : positive) {
if (!appendable(current_balance, tablet)) {
cout << "impossible\n";
return;
}
current_balance += tablet.balance;
order.push_back(tablet.index);
}
for (const Tablet& tablet : negative) {
if (!appendable(current_balance, tablet)) {
cout << "impossible\n";
return;
}
current_balance += tablet.balance;
order.push_back(tablet.index);
}
if (current_balance != 0) {
cout << "impossible\n";
return;
}
for (int index : order) {
cout << index << '\n';
}
}
} // namespace
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
solve();
return 0;
}
Source Files
Exact copied source-of-truth files. Edit solution.tex for the write-up and solution.cpp for the implementation.
\documentclass[11pt]{article}
\usepackage[margin=1in]{geometry}
\usepackage[T1]{fontenc}
\usepackage[utf8]{inputenc}
\usepackage{amsmath,amssymb,amsthm}
\usepackage{enumitem}
\title{ICPC World Finals 2021\\H. Prehistoric Programs}
\author{}
\date{}
\begin{document}
\maketitle
\section*{Problem Summary}
Each tablet is a parentheses string. We may permute the tablets, then concatenate all strings. We need
the final concatenation to be a correct bracket sequence:
\begin{itemize}[leftmargin=*]
\item every prefix must have nonnegative balance;
\item the total balance must be zero.
\end{itemize}
\section*{Per-Tablet Statistics}
For one string, define:
\begin{itemize}[leftmargin=*]
\item $\text{bal}$: total balance, where \texttt{(} is $+1$ and \texttt{)} is $-1$;
\item $\text{minPref}$: the minimum prefix balance inside that string.
\end{itemize}
If the current balance before appending this string is $B$, then the string is safe to append exactly when
\[
B + \text{minPref} \ge 0.
\]
After appending it, the new balance becomes
\[
B + \text{bal}.
\]
\section*{Split into Two Groups}
Strings with nonnegative total balance are helpful: once we append them, the available balance never gets
smaller in the long run. Strings with negative total balance are dangerous and should be postponed.
So we split all tablets into:
\begin{itemize}[leftmargin=*]
\item \textbf{positive group}: $\text{bal} \ge 0$;
\item \textbf{negative group}: $\text{bal} < 0$.
\end{itemize}
If a valid order exists at all, there is one where all positive-group strings come first and all
negative-group strings come last.
\section*{How to Sort the Positive Group}
Inside the positive group, we want the least demanding strings first: the ones that require the smallest
starting balance. That means sorting by
\[
\text{minPref}
\]
in decreasing order.
Then we greedily append them in that order, checking
\[
B + \text{minPref} \ge 0.
\]
\section*{How to Sort the Negative Group}
For negative-balance strings, the right measure is not just $\text{minPref}$, because after appending the
string we lose some balance permanently.
The standard way to view this group is to reverse each string and swap open/close parentheses. This turns
it into a positive-balance problem. Under that transformation, the relevant key becomes
\[
\text{bal} - \text{minPref}.
\]
Therefore the negative group should be sorted by
\[
\text{bal} - \text{minPref}
\]
in decreasing order, and then greedily appended in that order.
\section*{Algorithm}
\begin{enumerate}[leftmargin=*]
\item For each string, compute $\text{bal}$ and $\text{minPref}$.
\item If the total balance over all strings is not zero, output \texttt{impossible}.
\item Split strings into the positive and negative groups.
\item Sort:
\begin{itemize}[leftmargin=*]
\item positive group by decreasing $\text{minPref}$;
\item negative group by decreasing $\text{bal} - \text{minPref}$.
\end{itemize}
\item Traverse first the sorted positive group, then the sorted negative group.
\item Maintain the current balance $B$. If some string has
\[
B + \text{minPref} < 0,
\]
output \texttt{impossible}.
\item Otherwise append it, update
\[
B \leftarrow B + \text{bal},
\]
and record the index.
\end{enumerate}
\section*{Correctness Proof}
We prove that the algorithm outputs a valid permutation exactly when one exists.
\paragraph{Lemma 1.}
If a string with statistics $(\text{bal}, \text{minPref})$ is appended when the current balance is $B$, then
the concatenation remains prefix-valid if and only if
\[
B + \text{minPref} \ge 0.
\]
\paragraph{Proof.}
Inside the string, every prefix changes the balance by exactly the corresponding prefix balance of that
string. The worst such drop is $\text{minPref}$. So all resulting prefixes stay nonnegative exactly when
the minimum possible one, namely $B+\text{minPref}$, is nonnegative. \qed
\paragraph{Lemma 2.}
If a valid ordering exists, then there also exists a valid ordering in which every string with
$\text{bal} \ge 0$ appears before every string with $\text{bal} < 0$.
\paragraph{Proof.}
A nonnegative-balance string never reduces the total available balance after it is appended, while a
negative-balance string does. Swapping a negative string that appears before a nonnegative string cannot
make the future balance smaller at the point where the nonnegative string starts. Repeating this exchange
process pushes all nonnegative strings to the front without destroying validity. \qed
\paragraph{Lemma 3.}
Among strings with $\text{bal} \ge 0$, sorting by decreasing $\text{minPref}$ is optimal for greedy
construction.
\paragraph{Proof.}
Take two such strings $A$ and $B$ with $\text{minPref}(A) \ge \text{minPref}(B)$. If some current balance
$X$ can append $B$ first, then
\[
X + \text{minPref}(B) \ge 0.
\]
Since $\text{minPref}(A)$ is at least as large, $A$ is also appendable from $X$. After appending $A$, the
balance does not decrease overall because $\text{bal}(A)\ge0$, so whatever was possible after $B$ remains
possible after $A$. Thus placing the larger $\text{minPref}$ first is never worse. \qed
\paragraph{Lemma 4.}
Among strings with $\text{bal} < 0$, sorting by decreasing $\text{bal} - \text{minPref}$ is optimal.
\paragraph{Proof.}
Reverse each negative string and swap every parenthesis. This transforms it into a string with nonnegative
total balance. Under this transformation, the role of $\text{minPref}$ becomes exactly
\[
\text{bal} - \text{minPref}.
\]
So by Lemma 3, the transformed strings should be sorted by decreasing that quantity. Reversing the
transformation gives exactly the claimed order for the original negative strings. \qed
\paragraph{Theorem.}
The algorithm outputs a valid permutation if and only if one exists.
\paragraph{Proof.}
By Lemma 2, it is enough to consider orders with all positive strings first and all negative strings last.
By Lemma 3, the chosen order is optimal for the positive group, and by Lemma 4 it is optimal for the
negative group. During the final scan, Lemma 1 exactly characterizes whether each next string can be
appended safely.
Therefore, if the algorithm fails, no valid ordering exists. If it succeeds, every prefix stays
nonnegative and the total balance is zero, so the produced order is a correct bracket sequence. \qed
\section*{Complexity Analysis}
Let $L$ be the total number of parentheses in the input. Computing all statistics takes $O(L)$ time.
Sorting the tablets takes $O(n \log n)$ time. The memory usage is $O(n)$.
\section*{Implementation Notes}
\begin{itemize}[leftmargin=*]
\item We never need to store the full strings after computing $(\text{bal}, \text{minPref})$.
\item The final total balance must be zero; otherwise a correct bracket sequence is impossible no matter
how we permute the tablets.
\end{itemize}
\end{document}
#include <bits/stdc++.h>
using namespace std;
namespace {
struct Tablet {
int index;
int balance;
int min_prefix;
};
bool appendable(int current_balance, const Tablet& tablet) {
return current_balance + tablet.min_prefix >= 0;
}
void solve() {
int n;
cin >> n;
vector<Tablet> positive;
vector<Tablet> negative;
positive.reserve(n);
negative.reserve(n);
long long total_balance = 0;
for (int i = 1; i <= n; ++i) {
string s;
cin >> s;
int balance = 0;
int min_prefix = 0;
for (char ch : s) {
balance += (ch == '(' ? 1 : -1);
min_prefix = min(min_prefix, balance);
}
total_balance += balance;
Tablet tablet = {i, balance, min_prefix};
if (balance >= 0) {
positive.push_back(tablet);
} else {
negative.push_back(tablet);
}
}
if (total_balance != 0) {
cout << "impossible\n";
return;
}
sort(positive.begin(), positive.end(), [](const Tablet& lhs, const Tablet& rhs) {
return lhs.min_prefix > rhs.min_prefix;
});
sort(negative.begin(), negative.end(), [](const Tablet& lhs, const Tablet& rhs) {
return lhs.balance - lhs.min_prefix > rhs.balance - rhs.min_prefix;
});
vector<int> order;
order.reserve(n);
int current_balance = 0;
for (const Tablet& tablet : positive) {
if (!appendable(current_balance, tablet)) {
cout << "impossible\n";
return;
}
current_balance += tablet.balance;
order.push_back(tablet.index);
}
for (const Tablet& tablet : negative) {
if (!appendable(current_balance, tablet)) {
cout << "impossible\n";
return;
}
current_balance += tablet.balance;
order.push_back(tablet.index);
}
if (current_balance != 0) {
cout << "impossible\n";
return;
}
for (int index : order) {
cout << index << '\n';
}
}
} // namespace
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
solve();
return 0;
}