SWAPPERM2 - Editorial

PROBLEM LINK:

Practice
Contest: Division 1
Contest: Division 2
Contest: Division 3
Contest: Division 4

Author: raysh07
Tester: archit
Editorialist: raysh07

DIFFICULTY:

Easy-Medium

PREREQUISITES:

None

PROBLEM:

Given a permutation P, define f(P) = lexicographically smallest possible permutation possible by doing the following steps:

  • for each i = 1...N, either skip this, or choose X such that P_X = i or P_{X + 1} = i and then swap P_X and P_{X + 1}.

Find f(P).

EXPLANATION:

Because this is a lexicographic order minimization problem, we should try to build the answer one element at a time.

So, the natural first question is: what elements can be brought to index 1?

Suppose the prefix looks like A_1, A_2, ...., A_K, X and we ask ourselves the question whether it is possible to bring X to the start. Here we may assume X is a prefix minima, because otherwise it is clearly non-optimal.

On the X-th move, we swap A_K and X. After that, just like in SWAPPERM1, we have to depend on the A_i to be able to swap with X, since X cannot be swapped anymore.

This means that A_1 > A_2 > ... > A_{K - 1} must hold, because we require that the A_i-th move occur before the A_{i - 1}-th move.

This condition is both necessary and sufficient to be able to get X to the first position (along with X being prefix minima).

Thus, let P be the length of the longest decreasing prefix. Then, the first element will be either A_P or A_{P + 2} depending on whichever is smaller. (Because A_1, A_2, ..., A_{P - 1}, A_{P + 1} are larger than A_P, and A_i for i \ge P + 3 do not satisfy the decreasing condition)


What happens after we bring X to the beginning? Let’s think of 2 cases, depending on whether we bring the index P or P + 2.

  • Case 1 - The index P is brought to 1:
    Then, we did not affect any indices greater than P + 1. They still have their swappability maintained. Also, only A_{P - 1} can still be swapped with other elements (which is now present at A_P), the others become fixed. However, if we swap A_{P - 1} with T (using T to swap, this restriction is not present if we use A_p to swap) later on, it must satisfy T > A_P because we require the T-th swap to occur after the A_p-th swap.

  • Case 2 - The index P + 2 is brought to 1:
    A similiar analysis follows. All indices \ge P + 3 are intact. The element A_{p + 1} may be swapped and again we get some restriction on T.


We can just simulate this process of finding the longest decreasing sequence, and then trying the 2 options of P or P + 2. It will work in amortized O(N) time. We maintain a vector used_i that denotes the maximum element somebody has been swapped with, so that we can judge whether a swap is ok or not.

TIME COMPLEXITY:

\mathcal{O}(N) per testcase.

CODE:

Editorialist's Code (C++)
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF (int)1e18

mt19937_64 RNG(chrono::steady_clock::now().time_since_epoch().count());

vector <int> g(vector <int> p){
    int l = 0;
    int n = p.size();
    
    vector <int> ans;
    
    // the last thing may not be swappable anymore 
    vector <int> used(n + 1, 0); // must be strictly increasing 
    
    while (l < n){
        int r = l;
        while (r + 1 < n && p[r + 1] < p[r]){
            r++;
        }
        
        bool take_this = true;
        if (r + 2 < n && p[r + 2] < p[r]){
            take_this = false;
        }
        
        bool flag = false;
        
        if (take_this){
            if (l == r){
                ans.push_back(p[l]);
                l++;
            } else {
                if (used[p[r - 1]] > p[r]){
                    flag = true;
                }
                used[p[r - 1]] = p[r];
                ans.push_back(p[r]);
                for (int i = l; i < r - 1; i++){
                    ans.push_back(p[i]);
                }
                swap(p[r - 1], p[r]);
                l = r;
            }
        } else {
            if (used[p[r + 1]] > p[r + 2]){
                flag = true;
            }
            used[p[r + 1]] = p[r + 2];
            ans.push_back(p[r + 2]);
            for (int i = l; i <= r; i++){
                ans.push_back(p[i]);
            }
            l = r + 2;
            swap(p[l], p[l - 1]);
        }
        
        if (flag){
            ans.push_back(p[l]);
            l++;
        }
    }
    
    return ans;
}

void Solve() 
{
    int n; cin >> n;
    
    vector <int> p(n);
    for (int i = 0; i < n; i++){
        cin >> p[i];
    }
    
    auto fast_ans = g(p);
    for (auto x : fast_ans){
        cout << x << " ";
    }
    cout << "\n";
}

int32_t main() 
{
    auto begin = std::chrono::high_resolution_clock::now();
    ios_base::sync_with_stdio(0);
    cin.tie(0);
    int t = 1;
    // freopen("in",  "r", stdin);
    // freopen("out", "w", stdout);
    
    cin >> t;
    for(int i = 1; i <= t; i++) 
    {
        //cout << "Case #" << i << ": ";
        Solve();
    }
    auto end = std::chrono::high_resolution_clock::now();
    auto elapsed = std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin);
    cerr << "Time measured: " << elapsed.count() * 1e-9 << " seconds.\n"; 
    return 0;
}