MINOVER - Editorial

PROBLEM LINK:

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

Author: iceknight1093
Editorialist: iceknight1093

DIFFICULTY:

Simple

PREREQUISITES:

None

PROBLEM:

You are given arrays A and B, of lengths N and M.
In one move, you can replace some subarray of A of length M, with a cyclic rotation of B.
Find the lexicographically minimum possible array A.

All the elements of A are distinct, as are the elements of B.

EXPLANATION:

First, it’s easy to see that if we ever do a replacement, it must be with the lexicographically minimum cyclic rotation of B.
Since the elements of B are distinct, this minimum rotation is obtained by simply rotating the array so that the minimum element of B is at the start, easily done in \mathcal{O}(M).
Our operation is now just “replace a subarray of A with B”.

Now, let’s look at how A can be minimized.
Since we’re working with lexicographic order, a simple greedy algorithm works here: for each i from 1 to N-M+1, if replacing the subarray starting at i with B is good, do it.

Of course, a direct implementation of this would take \mathcal{O}(NM) time which is too slow, so we need to be smarter about it.

Let’s analyze what’s actually going on.
For each i from 1 to N,

  • If A_i \lt B_1, it’s optimal to not replace here since we’d only increase the value.
  • If A_i \gt B_1, it’s optimal to replace here. Note that this will affect comparisons of following elements.
  • If A_i = B_1, it may or may not be optimal to replace starting here, and we have to look at further elements to decide.

Now, observe that if we ever do perform a replacement at index i, we’ll set A_{i+1} to be B_2 (which is strictly larger than B_1).
This means it’s then optimal to perform the replacement at index i+1 as well, then at index i+2, i+3, and so on.

That is, if the first time we perform a replacement is at index L, we’ll end up performing a replacement for every valid index \geq L.
This is a useful observation because it allows us to easily simulate all the operations:

  • The last M elements of A will just be B itself, as a result of the final replacement.
  • For each i in the range [L, N-M+1], A_i will just equal B_1.
  • Elements of A before L will be unchanged.

That is, the final array will look like

[A_1, A_2, \ldots, A_{L-1}, B_1, B_1, \ldots, B_1, B_2, B_3, \ldots, B_M]

So, if L is known, the final array can easily be constructed in \mathcal{O}(N) time.
Our task is now thus just to find L quickly.

However, looking at the greedy algorithm, this is not too hard.
L will equal either the leftmost index with a value greater than B_1, or an index with a value that’s equal to B_1.
Since the elements of A are distinct, there’s at most one such index in the second case, so we only have at most two candidates for L.

This means a simple implementation is to simply find both candidates for L, generate the final array in both cases, and print the minimum among them.


Note that M = 1 is an edge case for the above solution.
However, it’s easy to handle this separately: there’s only one element in B, so it’s clearly optimal to just set every A_i to \min(A_i, B_1), i.e. replace every element of A that’s larger than B_1, by B_1.

TIME COMPLEXITY:

\mathcal{O}(N) per testcase.

CODE:

Editorialist's code (C++)
// #include <bits/allocator.h>
// #pragma GCC optimize("O3,unroll-loops")
// #pragma GCC target("avx2,bmi,bmi2,lzcnt,popcnt")
#include "bits/stdc++.h"
using namespace std;
using ll = long long int;
mt19937_64 RNG(chrono::high_resolution_clock::now().time_since_epoch().count());
 
int main()
{
    ios::sync_with_stdio(false); cin.tie(0);
 
    int t; cin >> t;
    while (t--) {
        int n, m; cin >> n >> m;
        vector<int> a(n), b(m);
        for (int &x : a) cin >> x;
        for (int &x : b) cin >> x;
 
        rotate(begin(b), ranges::min_element(b), end(b)); // Bring the smallest element of b to the front
        if (m == 1) {
            for (int i = 0; i < n; ++i) cout << min(a[i], b[0]) << ' ';
            cout << '\n';
            continue;
        }
        
        for (int i = 0; i+m <= n; ++i) {
            if (a[i] < b[0]) continue;
 
            bool replace = true;
            if (a[i] == b[0]) {
                replace = false;
                for (int j = 0; j < m; ++j) {
                    if (a[i+j] == b[j]) continue;
                    if (a[i+j] < b[j]) break;
                    replace = true;
                    break;
                }
            }
 
            if (!replace) continue;
 
            for (int j = i; j < n; ++j)
                a[j] = b[0];
            for (int j = 0; j < m; ++j)
                a[n-1-j] = b[m-1-j];
            break;
        }
 
        for (int x : a) cout << x << ' ';
        cout << '\n';
    }
}

Correct me if I am wrong, but I feel the editorial code has the worst time complexity of O(n×m)O(n \times m) per test case. So, as per the given constraints, it should not work.

Please help me understand this better.

1 Like

I guess there is something wrong with the question. Either the test cases were weak and some network glitch, as the Brute Force solution : https://www.codechef.com/viewsolution/1132142555 of user https://www.codechef.com/users/tidy_grace_22 got AC during the contest by chance but it exceeds the time limit on sub-task 6

here is the submission link where I just copy pasted his AC solution (obviously after the contest) and got TLE :zipper_mouth_face: : https://www.codechef.com/viewsolution/1132352639

@iceknight1093 Please look into this .

3 Likes
if (a[i] == b[0]) {
                replace = false;
                for (int j = 0; j < m; ++j) {
                    if (a[i+j] == b[j]) continue;
                    if (a[i+j] < b[j]) break;
                    replace = true;
                    break;
                }
            }

you are saying O(n*m) due to this but this loop will run atmax once.
bcz elements are distinct. Think…