Subtask 1: Try all N!=N⋅(N−1)⋯1 possible p in lexicographical order, and see if they can produce h. In Python, we can use itertools.permutations to do this easily.
Ben's Python code:
def P_to_H(P): H = [] while len(P) > 1: if P[0] > P[-1]: H.append(P[1]) P = P[1:] else: H.append(P[-2]) P = P[:-1] return H import itertools def solve(): N = int(input()) H = list(map(int, input().split())) for P in itertools.permutations(range(1, N + 1)): if P_to_H(P) == H: print(" ".join(map(str, P))) return print(-1) T = int(input()) for _ in range(T): solve()
Subtask 2: If we are given the values of p1 and pN, we can uniquely determine the rest of p by going through h one element at a time. In particular, when N>2,
and then we can process h2,h3,…,hN−1 similarly. At the end, we should check if p forms a permutation or not and actually produces h; if so, we update our answer. The time complexity is O(N3) per test.
Ben's Python code:
def P_to_H(P): H = [] while len(P) > 1: if P[0] > P[-1]: H.append(P[1]) P = P[1:] else: H.append(P[-2]) P = P[:-1] return H def solve(): N = int(input()) H = list(map(int, input().split())) ans = None for l_val in range(1, N + 1): for r_val in range(1, N + 1): cand = [-1] * N l = 0 r = N - 1 cand[l] = l_val cand[r] = r_val for h in H: if cand[l] > cand[r]: l += 1 cand[l] = h else: r -= 1 cand[r] = h if len(set(cand)) == N and P_to_H(cand) == H: if ans is None: ans = cand ans = min(ans, cand) if ans is None: print(-1) return print(" ".join(map(str, ans))) T = int(input()) for _ in range(T): solve()
Subtask 3: Consider the process of producing h from p. Note that the smallest element of p, 1, will never be removed. So when two elements remain in p, one must be 1, and 1 will be the last element written down. Thus, we must have hN−1=1, or else we can output −1 and exit. For the remainder of the problem, we will not use the last element of h, so we discard the last element of h and assume that h has length N−2 going forward.
If the answer is not −1, the lexicographically minimum p must satisfy p1<pN, or else reversing p will give a lexicographically smaller permutation.
Can there be more than one permutation p satisfying p1<pN that maps to the same h? If we print the mapping from p to h for all p for small N (e.g., N=4) using our solution for subtask 1, we observe that all h are distinct. So it seems true if there exists some p that maps to h, p must be unique up to reversal.
def P_to_H(P): H = [] while len(P) > 1: if P[0] > P[-1]: H.append(P[1]) P = P[1:] else: H.append(P[-2]) P = P[:-1] return H import itertools N = 4 for P in itertools.permutations(range(1, N + 1)): if P[0] < P[-1]: print(P, P_to_H(P)[:-1])
Output:
(1, 2, 3, 4) [3, 2] (1, 2, 4, 3) [4, 2] (1, 3, 2, 4) [2, 3] (1, 3, 4, 2) [4, 3] (1, 4, 2, 3) [2, 4] (1, 4, 3, 2) [3, 4] (2, 1, 3, 4) [3, 1] (2, 1, 4, 3) [4, 1] (2, 3, 1, 4) [1, 3] (2, 4, 1, 3) [1, 4] (3, 1, 2, 4) [2, 1] (3, 2, 1, 4) [1, 2]
Looking at the permutations above (or from working out a few cases by hand), we can further observe that
Here's a way to see why these observations are true. If we perform the following operation N−2 times, we will always end up writing down a permutation of p2…pN−1:
Operation: Choose to do exactly one of the following:
Once we've determined the numbers at the ends of p, we can determine the remainder of p in the same way as subtask 2. In fact, it turns out that as long as all elements of h are distinct, there is some p mapping to it.
Ben's Python code:
def solve(): N = int(input()) H = list(map(int, input().split())) if H[-1] != 1: print(-1) return H = H[:-1] remaining = [False] + [True] * N for h in H: if not remaining[h]: print(-1) return remaining[h] = False ends = [x for x in range(1, N + 1) if remaining[x]] ans = [-1] * N l, r = 0, N - 1 ans[l], ans[r] = ends for h in H: if ans[l] > ans[r]: l += 1 ans[l] = h else: r -= 1 ans[r] = h print(" ".join(map(str, ans))) T = int(input()) for _ in range(T): solve()
Chongtian's C++ code:
#include <bits/stdc++.h> using namespace std; int main() { int T; cin >> T; while (T--) { int n; cin >> n; vector<int> h(n - 1); for (int &i : h) cin >> i; vector<bool> has(n + 1); for (int i : h) has[i] = 1; vector<int> not_has; for (int i = 1; i <= n; i++) { if (!has[i]) { not_has.push_back(i); } } int cnt_one = count(h.begin(), h.end(), 1); if (not_has.size() > 2 || h.back() != 1 || (not_has.size() == 2 && cnt_one != 2)) { cout << -1 << endl; continue; } vector<int> p(n); if (not_has.size() == 1) { // this must mean 1 is in the corner p[0] = 1; p[n - 1] = not_has[0]; } else { p[0] = not_has[0]; p[n - 1] = not_has[1]; } int l = 0, r = n - 1; for (int i = 0; i < n - 1; i++) { if (p[l] > p[r]) { p[++l] = h[i]; } else { p[--r] = h[i]; } } for (int i = 0; i < n; i++) { if (i) cout << " "; cout << p[i]; } cout << "\n"; } }
Bonus:
In this problem, we constructed a bijection (one-to-one mapping) between all N!/2 line graphs with vertex set 1…N and all N!/2 sequences of length N−2 consisting only of 1…N with no duplicates. More generally, there is a bijection between all NN−2 trees with vertex set 1…N and all NN−2 sequences of length N−2 consisting only of 1…N (Prufer sequence). The mapping from trees to sequences is essentially the same as described in this problem; N−2 times, take the highest leaf, write down the vertex it is adjacent to, and remove the leaf. Try to figure out the reverse mapping (sequence to trees) on your own.