Sunday, February 1, 2026

Distinct Values Subarrays

\(I\) is an index set: \(I=[0,n) = \{0, 1, \dots, n - 1 \}\)

\([i,j] = \{i,i+1,\dots,j\}\), \(|[i,j]| = j - i + 1\)

\((I, F)\) is a set family: \((I, F)\), \(F = \{[i,j] \mid i,j \in I, i \leq j \} = \cup_{j \in I} \{[i,j] \mid i \in I, i \leq j\}\)

\(|F|=|\cup_{j \in I} \{[i,j] \mid i \in I, i \leq j\}|=\sum_{j \in I}|\{[i,j] \mid i \in I, i \leq j\}|=\sum_{j=0}^{n-1}j+1\)

\(|F|=\binom{n + 1}{2}\)

\(A\) is some finite set: \(A = \{\text{a}, \text{b}, \dots\}\). \(s\) is a sequence over \(A\): \(s \in A^{I}\)

\(F_s\) is the family of index sets corresponding to all subarrays of \(s\) that have distinct elements: \(F_s=\{[i,j] \mid i,j \in I, i \leq j, \land_{i,j \in I, i \neq j} s(i) \neq s(j)\}\).

\(F_s\) is a finite, hereditary (downward-closed) set family, so it equals the union of the ideals of its maximal elements. It's not an independence system though because the empty set is not in \(F_s\) The maximal elements of \(F_s\) are index sets corresponding to distinct subarrays, that are not included in any other index set: these subarrays can't be extended without repeating an element. For example, consider this sequence: \(s\equiv\langle \text{abcbd}\rangle\). The maximal distinct subarrays are \(\langle \text{abc}\rangle\) and \(\langle \text{cbd}\rangle\). The corresponding index sets are \(\{0,1,2\}\) and \(\{2,3,4\}\). \(F_s = I(\{0,1,2\}) \cup I(\{2,3,4\})\).

\(I(X)=\{Y \mid Y \in F_s, Y \subseteq X\}\) is the ideal of \(X\). A "sliding window" algorithm for computing \(|F_s|\) follows from this observation: expand a subarray to find the maximal distinct subarrays and use the inclusion-exclusion principle to count their union: \(|F_s|=|I(J_1)| + |I(J_2)| - |I(J_1) \cap I(J_2)|\). This is easy to implement because only adjacent maximal distinct subarrays overlap.


Distinct Values Subarrays problem on CSES: https://cses.fi/problemset/task/3420/

My solution program: https://cses.fi/paste/10b865ef59bbe20ef6257a/


Here's another solution program that is only different in the way it computes the combinations: https://cses.fi/paste/10b865ef59bbe20ef6257a/


Monday, October 13, 2025

Baltic Olympiad in Informatics 2024 (BOI 2024) problem: jobs

Problem Statement 

Maximize the sum of profits for a subset of jobs S:

max S⊆{1,...,n}i ∈ S xi

such that if job i is picked, then job p(i) is also picked:

i ∈ S → p(i) ∈ S

and there exists an order σ for the jobs in S in which no debt is incurred for all prefixes of the order:

∃ σ ( s + ∑kj=1 xσ(j) ≥ 0,   k = 1, ..., |S| )
Without the second constraint, the answer is simply just y(0), where y(i) = x(i) + max(0, y(first child job of job i)) + max(0, y(second child job of job i)) + … + max(0, y(last child job of job i)). That can easily be computed bottom-up since p(i) < i. This case is equivalent to the case of having enough initial euros to foot any possible intermediate debt: s = 10^18. Note, that x(0) = s and p(0) = 0: job 0 is a dummy job whose profit is the initial balance.

The final job picks will form a single connected component rooted at job 0 because of the first constraint: if job i is picked, then job p(i) is picked. So, picking a job is akin to merging that job with its parent. This is easy if both x(i), x(p(i)) > 0, but not  if x(i) > 0 and x(p(i)) < 0. So, you have to remember the minimum "cost to take" the new merged job that has profit x(i) + x(p(i)). You'd also need to remember the connected components across merges. Both can be done with auxiliary memory, e.g. cost_to_take(i) and a union-find data structure (U(i), find, merge).
 
How do you go about merging jobs to construct the solution then? Do you construct in a top-down manner by growing the solution at its' edges? I found that direction to be a "red herring", and all my attempts for such a job-merging strategy broke down for this example:
6 0
4 0
-3 
-1 2
-1 2
3 3
3 4
 
If x(i) > 0 and job p(i) is picked, then job i will certainly be picked. Further, if x(i) > 0 and cost_to_take(i)≤x(p(i)), then job i will certainly be picked if job p(i) is picked. There's more of these, but notice that these are true all across the job tree. It turns out that performing such job merges all across the tree greedily gives the answer.
 
The greedy job-merging solution is to merge the job that is least expensive to take and has positive profit with its parent job, i.e. out of all the jobs with x(i) > 0, take the one with the smallest cost_to_take(i) and merge it to its parent. I won't go into the actual casework for job-merging as it can be seen in the solution. Here's an additional description of the greedy algorithm. Also, there's a small catch for merging with the zeroth job.

Here's the code for my solution.

Additional observation and questions:
There's a greedy solution to this problem, but I did not reach it from this starting point (the optimization program). Is there some structure in this optimization program that I am missing? I suspect that there is and that exploiting it makes the greedy solution very apparent. But, I have not spotted it. I am wondering about this because there's lots of other optimization problems with greedy solutions, e.g. huffman coding (minimize expected codeword length), basis pursuit (maximize sparsity of signal decomposition), ... 
 
Possible bottom-up dynamic programming solution:
Let z(i, k) = maximum profit attainable if you take job i and have k euros initially. This is enough for a bottom-up dynamic programming solution (convince yourself of this). I could not put together an implementation for it though; someone else supposedly did. One interesting thing to note is that if the job subtree rooted at job i is replaced with another job tree that has the exact same z(i,k) profile, then the problem is unchanged. So, z(i,k) carries all information needed about a job tree for this problem.

Thursday, October 2, 2025

What's the k-th permutation of a sequence?

Set A is a finite totally ordered set of symbols. Because set A is finite and totally ordered, it's well-ordered. So, I'll just say A = {1, 2, ..., n} for some n. (1, 2, ..., n) is a finite n-long sequence over A. Consider its set of permutations and the lexicographic order over it. What's the kth permutation with respect to the order?

Notice that there's n! many n-permutations and (n-1)! many n-permutations that begin with a chosen first number. For example, there are (n-1)! n-permutations that are of form (1, ...) and (n-1)! n-permutations that are of form (2, ...) and (n-1)! n-permutations that are of form (3, ...) and so on. So, the corresponding number for the first permutation of form (x, ...) is immediately apparent.

Let k = k' + 1
k' = 0 for (1,2,3, ..., n)
k' = (n - 1)! for (2, 1, 3, ..., n)
k' = 2(n - 1)! for (3, 1, 2, ..., n)
...
k' = (n - 1)(n - 1)! for (n, 2, 3, ..., n-1)

So, if a sequence is of form (x,...), then it's number is greater than or equal to (x-1)(n-1)!

What then can we say about sequences of form (x,y, ...), x≠y? Is there a numbering for the pairs (x,y), x≠y for which I can make a similar bound? First, observe that there are n(n-1) = nP2 such pairs.

remark
The obvious intuition for nP2 is that by choosing one element and then another without replacement, there's n(n - 1) choices: nP2 = (n choose 1)(n-1 choose 1) = n(n - 1).

Here's a way to think about nP2 without the idea of "choosing": consider the set of all pairs A✕A. There's n2 many of those pairs. And, there's n of those pairs (x,y) in which x=y. so there's n2-n = n(n-1) pairs in A✕A - {(1,1), (2,2), ..., (n,n)}. That corresponds to choosing a pair without replacement. A geometric way to think of this is to consider how many cells are in an "n by n" grid minus the diagonal.

Observe that the set of permutations of the sequence (1,2, ..., n) is also the set of all n-long sequences whose elements are distinct. Notice, that this means that elements of a permutation sequence s are coupled by the following constraint/sentence: for all i and j in [1,n], if i ≠ j, then s(i) ≠ s(j). An interesting consequence of this coupling is that if you pick all other n-1 elements of the sequence the final element is fully determined.

This coupled-ness makes finding a desired numbering over the original permutation sequence very inconvenient. Consider the first sequence of form (2,1, ...) and the first sequence of form (2, 3, ...). The first sequences' corresponding number is (n - 1)(n - 2)!=(n - 1)!, but the second's corresponding number is n(n - 2)! . Finding this numbering would involve numbering the pairs A✕A - {(1,1), (2,2), ..., (n,n)} lexicographically, so the numberings for sequences (x, x - 1, ...) and (x, x + 1, ...) have to be adjacent by a factor of (n - 2)! .

second remark
Permutations with more inversions have a higher number. Can you characterize the of permutations with k exactly inversions? What about permutations with less than or equal to k inversions? What happens to a permutation's number when a swap is performed?

Still, there's an easy way to construct the desired correspondence between the n-permutations and [1, n!]. Naturally, it involves an intermediate sequence whose elements are decoupled.

First, I'll introduce a cool concept: the minimum excluded value. Say that S is a subset of a well-ordered set A. mex(S) is the minimum excluded value, i.e. the smallest value that is in A, but not in S. Here's a generalization: the kth smallest excluded value, which is the kth smallest value that is in A: kex(k,S). Notice that kex(1,S) = mex(S). Also, notice that kex(k, {1, ..., n}) = k.

One way to construct an n-permutation s1 is to

choose s1(0) by drawing the s2(0)th element from A = {1, ..., n},

and then choose s1(1) by drawing the s2(1)th element from A - {s1(0)}, 

and then choose s1(2) by drawing the s2(2)th element from A - {s1(0), s1(1)},

and so on.

The relation between s1 and s2 can be expressed neatly with kex:

s1(i) = kex( s2(i), A - {s1(0), ..., s1(i - 1)} )

Notice that the elements of this "picking sequence" s2 are decoupled, unlike the permutation sequence s1. Also, notice that s2(i) ∈ {1, 2, ..., n - i}. The decoupled-ness permits an easy numbering:

k' = (s2(0) - 1)(n - 1)! + ... + (s2(n - 1) - 1)0!

if s2'(i) = (s2(i)-1), then

k' = s2'(0)(n - 1)! + ... + s2'(n - 1)0!

This is a decomposition into a factorial numeral system, and the digits can be retrieved with division.


Here's accompanying Kotlin code

fun getPermutation(n: Int, k: Int): String {
        val _factorial : MutableList<Int> = MutableList(n) {0}
        _factorial[0] = 1
        for (i in 1..(n-1)) {
            _factorial[i] = _factorial[i-1] * i
        }
        val factorial : List<Int> = _factorial as List<Int>
        val permutation = StringBuilder()
        val picks : MutableList<Int> = mutableListOf<Int>()
        for (i in 1..n) {
            picks.add(i)
        }
        var kMinusOne : Int = k - 1 // k' is kMinusOne
        for (i in 1..n) {
            val s : Int = kMinusOne / factorial[n - i]
            kMinusOne = kMinusOne % factorial[n-i]
            permutation.append(picks[s])
            picks.removeAt(s)
        }
        return permutation.toString()
}


Related:

Cantor expansion

mixed-radix numeral systems, factorial number systems

Minimum excluded value (mex)

kth order statistics

Lehmer codes


Wednesday, October 1, 2025

Booting an Ubuntu Server disk image with QEMU

1. Download an Ubuntu server cloud qcow2 disk image.

wget https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img

2. Create cloud-init configuration files

Also, you can generate an SSH key pair and use it to login over SSH if you put the public key as I did below. cloud-init will automatically write it to ~/.ssh/authorized_keys
touch meta-data
touch network-data
touch vendor-data

cat << EOF > user-data
#cloud-config
users:
  - name: ubuntu
    sudo: ALL=(ALL) NOPASSWD:ALL
    groups: sudo
    shell: /bin/bash
    lock_passwd: false
    password: password
    ssh_authorized_keys:
      - <your public SSH key>

chpasswd:
  list: |
    ubuntu:password
  expire: False

ssh_pwauth: True
EOF

3. Create an ISO disk image out of those files

The volume id should be cidata. Here, I enabled the Rock Ridge and Joliet ISO 9660 extensions.

xorriso -as mkisofs \
    -output seed.img \
    -volid cidata -rational-rock -joliet \
    user-data meta-data network-config

4. Run the guest OS

Notice that the disk image that holds Ubuntu Server and the CD-ROM disk image are both "plugged in" to the machine being emulated. See the QEMU documentation for more help on writing the correct and fastest QEMU invocation for your platform and use.

qemu-system-x86_64 \
  -m 2G \
  -accel tcg \
  -drive file=noble-server-cloudimg-amd64.img,if=virtio,format=qcow2 \
  -drive file=seed.img,format=raw,media=cdrom \
  -netdev user,id=net0 \
  -device virtio-net-pci,netdev=net0,hostfwd=tcp::2222-:22 \ # you only need hostfwd if you're using ssh
  -nographic

Now, in the guest OS:

ubuntu@ubuntu:~$ uname -a
Linux ubuntu 6.8.0-90-generic #91-Ubuntu SMP PREEMPT_DYNAMIC Tue Nov 18 14:14:30 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux

In this case, the username is ubuntu, and the password is password. If you set up SSH, you can login without your password:

ssh -p 2222 -i private_key ubuntu@localhost

5.  Optionally, mount the host file-system onto guest's file system

Make sure that QEMU is compiled with support for the 9p protocol. Add this to the QEMU invocation:
-virtfs local,path=/Users/varunnawathey,mount_tag=share0,security_model=passthrough
And, in the guest, mount the file system tag
sudo mount -t 9p share0 /mnt

Relevant Documentation:

Tuesday, August 19, 2025

King's Summit

Atcoder ABC 419 problem C

observations
There are n points: x1=(r1,c1), x2=(r2,c2),...,xn=(rn,cn). The time it takes to get from point a=(ra,ca) to point b=(rb,cb) is the chebyshev distance between a and b: ||a-b||inf. This is length of the shortest "king-walk" between a and b on a chess-board. So, if all the persons meet at "summit" point s=(rs,cs), then it will take max(||s-x1||inf, ||s-x2||inf, ..., ||s-xn||inf) time, i.e. the longest time to reach the summit across all persons equals the total time it takes everyone to reach the summit. 

The chebyshev distance between points a and b is the Linf norm of the difference between a and b. The Linf norm selects the maximum component: ||a-b||inf=max(|ra-rb|,|ca-cb|). Additionally, |p| = max(-p, p). Hence, the Linf norm of ||a-b||inf = max(|ra-rb|,|ca-cb|) = max(ra-rb, rb-ra, ca-cb, cb-ca).

This leads to a big max expression:
time to reach summit becomes
= max(||s-x1||inf, ||s-x2||inf, ..., ||s-xn||inf)
= max(rs - rx1, rx1 - rs, cs - cx1, cx1 - cs,
           rs - rx2, rx2 - rs, cs - cx2, cx2 - cs,
           ...
           rs - rxn, rxn - rs, cs - cxn, cxn - cs)


Assume that the components of persons' starting positions are ordered:
rsmallest ≤ rsecond_smallest ≤ ... ≤ rbiggest
csmallest ≤ csecond_smallest ≤ ... ≤ cbiggest

Then, rs - rsmallest ≥ rs - rx2 ≥ ...rs - rbiggest. Exploiting this structure for the other maximum arguments, leads to this expression for the maximum time to summit:
= max(rs - rsmallest, rbiggest - rs, cs - csmallest, cbiggest - cs).

And, the minimum of this expression in terms of s=(rs,cs) is max( ceil((rbiggest - rsmallest) / 2), ceil((cbiggest - csmallest) / 2).

Friday, August 1, 2025

Codeforces union find problem involving cycles

Codeforces Round 1040 (Div. 2) Problem C

Observations

Given n ranges, the problem involves maximally covering the range [1, 2n] with those ranges, while jointly minimizing cycles between the endpoints of those ranges/ undirected edges. [1, 2n] will be maximally covered by taking all ranges, but this does not necessarily avoid cycles. The important observation is that all cycles have a minimum and maximum point, e.g. 1 and 4 in S = {(1, 2), (2, 4), (1, 4)}. There are always two "paths" to get from the minimum to the maximum. In the case of S, {(1, 2), (2, 4)} or {(1, 4)}. Getting from the minimum to the maximum while avoiding a cycle is then just a matter of take one path and only one path.

Solution

 Take ranges/ undirected edges greedily. Only take an edge (a, b) if b is not currently reachable from a, i.e. the other "path" has not already been taken