Baltic OI 2016 - Swap

Authors: Benjamin Qi, Andi Qu

Official Analysis

Approach 1

Complexity: O(Nlog2N)\mathcal{O}(N \log^2N) time with O(NlogN)\mathcal{O}(N \log N) memory.

Let the elements of AA be nodes of a graph and each potential swap be an edge between two nodes. Notice how this graph is a binary tree. We effectively want to perform some swaps to minimize the BFS order of this tree.

Let merge(A,B,C)\texttt{merge}(A, B, C) denote the tree with AA as the root and BB and CC as the subtrees of AA's left and right children respectively. We can compute merge(A,B,C)\texttt{merge}(A, B, C) in O(B+C)\mathcal{O}(|B| + |C|) time.

We can now formulate a basic DP state. Let dp[i][j]dp[i][j] be the version of node ii's subtree after some swaps such that Ai=jA_i = j initially and the BFS order of dp[i][j]dp[i][j] is minimal. Let ll and rr be node ii's left and right children respectively. The following recurrence holds:

dp[i][j]={merge(j,dp[l][Al],dp[r][Ar])if j<min(Al,Ar)merge(Al,dp[l][j],dp[r][Ar])if Al<min(j,Ar)min(merge(Ar,dp[l][j],dp[r][Al]),merge(Ar,dp[l][Al],dp[r][j]))otherwisedp[i][j] = \begin{cases} \texttt{merge}(j, dp[l][A_l], dp[r][A_r]) & \text{if } j < \min(A_l, A_r)\\ \texttt{merge}(A_l, dp[l][j], dp[r][A_r]) & \text{if } A_l < \min(j, A_r)\\ \min(\texttt{merge}(A_r, dp[l][j], dp[r][A_l]), \texttt{merge}(A_r, dp[l][A_l], dp[r][j])) & \text{otherwise} \end{cases}

The answer to the problem is thus dp[1][A1]dp[1][A_1]. If we compute this DP naively, we get a O(N2logN)\mathcal{O}(N^2 \log N) solution that uses (N2logN)\mathcal(N^2 \log N) memory (since O(subtree size)=O(NlogN)\mathcal{O}(\sum\text{subtree size}) = \mathcal{O}(N \log N)).

To improve this solution, notice that for some ii, we have j=A[k]j = A[k] only if kk is a child of an ancestor of ii. Since there are only O(logN)\mathcal{O}(\log N) ancestors of ii and each has at most 2 children, this allows us to cut the time (and memory) complexity down to O(Nlog2N)\mathcal{O}(N \log^2N)! For convenience, we still refer to the original DP state in the rest of this solution.

However, this DP still uses too much memory. There are two things we need to do to fix this:

  • Process the tree in reverse BFS order (i.e. starting from node NN and working back to node 1). This allows us to free up the memory used by dp[l]dp[l] and dp[r]dp[r] after we process some node ii. This cuts the memory used down to O(NlogN)\mathcal{O}(N \log N), but is still slightly too much.
  • Only compute the states that dp[1][A1]dp[1][A_1] depends on. For example, the value of dp[2][A1]dp[2][A_1] is irrelevant if A1<min(A2,A3)A_1 < \min(A_2, A_3).

These two optimizations save us just enough memory to get AC.

#include <bits/stdc++.h>
using namespace std;
int a[200002];
bool needed[200002][18][2];
vector<int> dp[200002][18][2], tmp1, tmp2;
void merge(vector<int> &ret, vector<int> &l, vector<int> &r) {
for (int i = 0, j = 1; i < l.size(); i += j, j <<= 1) {
for (int k = i; k < i + j && k < l.size(); k++) ret.push_back(l[k]);

Approach 2

Some magic DP. See the discussion on CF.

Time Complexity: O(Nlog2N)\mathcal{O}(N\log^2N), can be reduced to O(NlogN)\mathcal{O}(N\log N).

Memory Complexity: O(NlogN)\mathcal{O}(N\log N)

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ld = long double;
using db = double;
using str = string; // yay python!
using pi = pair<int, int>;
using pl = pair<ll, ll>;

Approach 3

Maintain some collection of heaps and compute the sequence in order. I think this is similar to what the official solution does, although I don't completely understand it.

Time Complexity: O(NlogN)\mathcal{O}(N\log N)

Memory Complexity: O(N)\mathcal{O}(N)

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ld = long double;
using db = double;
using str = string; // yay python!
using pi = pair<int, int>;
using pl = pair<ll, ll>;

Join the USACO Forum!

Stuck on a problem, or don't understand a module? Join the USACO Forum and get help from other competitive programmers!