boxcat

Permutation Cuts

A bitonic array, read backwards into the permutation that made it

Cut a permutation anywhere, take the larger element on each side, keep the smaller of the two. Do that at every cut and you get an array. The question is how many permutations produce a given one β€” and the answer falls out of noticing that the array can only have one shape.

The problem#

Given n𝑛 and an array aπ‘Ž of length nβˆ’1π‘›βˆ’1, count the permutations p𝑝 of (1...n)(1…𝑛) with

𝑣𝑖=min(max1≀𝑗≀𝑖𝑝𝑗,max𝑖+1≀𝑗≀𝑛𝑝𝑗)=π‘Žπ‘–
(1)

for every 1≀i≀nβˆ’11β‰€π‘–β‰€π‘›βˆ’1, modulo 998244353998244353. Both n𝑛 and the sum of n𝑛 over all tests go to 10^6106, so the budget is linear and the constant matters.

One of the two maxima is always n𝑛#

Write L_i=max_(j≀i)p_j𝐿𝑖=max𝑗≀𝑖𝑝𝑗 and R_i=max_(j>i)p_j𝑅𝑖=max𝑗>𝑖𝑝𝑗. As the cut moves right, L𝐿 can only grow and R𝑅 can only shrink. That much is immediate. The useful observation is sharper: let kπ‘˜ be the position holding the value n𝑛. Then n𝑛 is on the right of every cut before kπ‘˜ and on the left of every cut from kπ‘˜ onwards, so

π‘Žπ‘–={𝐿𝑖𝑖<π‘˜π‘…π‘–π‘–β‰₯π‘˜
(2)

In words:

a_iπ‘Žπ‘– is the maximum of whichever side does not contain n𝑛.

So aπ‘Ž is a prefix-maximum sequence glued to a suffix-maximum sequence β€” non-decreasing, then non-increasing. It is bitonic, and it has no choice about it.

The peak is nβˆ’1π‘›βˆ’1#

The two halves either side of position kπ‘˜ partition {1,...,n}βˆ–{n}{1,…,𝑛}βˆ–{𝑛} between them. One of them therefore contains nβˆ’1π‘›βˆ’1, and

max(π‘Žπ‘˜βˆ’1,π‘Žπ‘˜)=π‘›βˆ’1
(3)

Exactly one of the two, never both, since nβˆ’1π‘›βˆ’1 sits in exactly one half. That single fact does a lot of work below: it says the peak of the bitonic array is pinned to a known value, so an array whose peak is anything else is unachievable and the answer is zero.

Reading p𝑝 back off aπ‘Ž#

Walk the left half outward from index 11. Since a_i=max(p_1,...,p_i)π‘Žπ‘–=max(𝑝1,…,𝑝𝑖) there,

  • if a_i>a_(iβˆ’1)π‘Žπ‘–>π‘Žπ‘–βˆ’1, position i𝑖 holds a new left-to-right maximum, and its value is forced to be exactly a_iπ‘Žπ‘–;
  • if a_i=a_(iβˆ’1)π‘Žπ‘–=π‘Žπ‘–βˆ’1, position i𝑖 holds something that did not break the record β€” any unused value below a_iπ‘Žπ‘–, and we get to choose.

The right half is the mirror image. From a_j=max(p_(j+1),...,p_n)π‘Žπ‘—=max(𝑝𝑗+1,…,𝑝𝑛), a step a_j>a_(j+1)π‘Žπ‘—>π‘Žπ‘—+1 forces p_(j+1)=a_j𝑝𝑗+1=π‘Žπ‘—, and a_j=a_(j+1)π‘Žπ‘—=π‘Žπ‘—+1 leaves a free choice below a_jπ‘Žπ‘—.1Index j𝑗 on the right names position j+1𝑗+1, not j𝑗. Off-by-one here is the easiest way to write a solution that is right on palindromic inputs and wrong on everything else.

Each of the nβˆ’1π‘›βˆ’1 entries of aπ‘Ž names exactly one position of p𝑝, and the one position left over is kπ‘˜, which holds n𝑛.

Counting#

Process the entries in increasing order of value. Because EquationΒ 2 makes aπ‘Ž rise and then fall, the two smallest unprocessed entries are always the two ends, so a pair of pointers walking inward visits the values in sorted order without ever sorting anything.

Keep a counter t𝑑 of positions already assigned. When the value v𝑣 at the current end is strictly larger than the previous one, that position is a record and its value is forced: one way. When v𝑣 repeats the previous value, the position takes any unused value strictly below v𝑣. Of the vβˆ’1π‘£βˆ’1 candidates, tβˆ’1π‘‘βˆ’1 are already spent β€” every assigned position so far holds a value ≀v≀𝑣, and exactly one of them holds v𝑣 itself β€” leaving

π‘£βˆ’π‘‘
(4)

choices β€” EquationΒ 4. If that count reaches zero the array is unachievable. Multiply, and finish with a factor of 22: when the pointers meet, one entry and two positions remain, and the last freedom is which side of the final cut takes n𝑛.

The whole thing is a single inward walk: O(n)𝑂(𝑛) time and no allocation beyond the input.

The code#

Three of the four early exits are the conditions above β€” the peak must be nβˆ’1π‘›βˆ’1 by EquationΒ 3, values must not decrease along the walk, and a repeat must leave something to choose.

const PRIME_MOD: u64 = 998_244_353;

fn count(n: usize, xs: &[u32]) -> u64 {
    let (mut i, mut j, mut prev) = (0, n - 2, 0);
    let mut res = 1;
    let mut taken = 0;

    loop {
        // Equal ends can only be the peak, and the peak is pinned to n-1:
        // two different halves cannot both have maximum v.
        if xs[i] == xs[j] && xs[i] as usize != n - 1 {
            return 0
        };
        if i == j { return res * 2 % PRIME_MOD };

        let left = xs[i] < xs[j];
        let v = if left { xs[i] } else { xs[j] };

        if v < prev { return 0 };          // not bitonic
        if taken >= v { return 0 };        // nothing left below v to choose

        if v == prev {
            res *= (v - taken) as u64;     // a non-record position
            res %= PRIME_MOD;
        }

        prev = v;
        if left { i += 1 } else { j -= 1 };
        taken += 1;
    }
}

The equality test at the top of the loop is doing double duty, which is worth pausing on. While the pointers are apart it rejects an array claiming two halves with the same maximum. When they meet it is trivially true, and so becomes the check that the peak really is nβˆ’1π‘›βˆ’1 before the answer is returned.