TIP

I spent a lot of time writing it in LaTeX, this page will stay here at least to test the rendering of the math formulas on the blog!

Also, if you find a mistake in any solution, I can buy you a beer 🍺.

Problem 1

is false, counterexample:

Problem 2

Also, and , therefore:

Additionally, , so:

Thus:

Problem 3

To verify: compute the limits.

From the top — the slowest growing function, from the bottom — the fastest growing.

Problem 4

Let’s construct a graph: for each element of the permutation , we draw a directed edge to .

Selection sort will perform swaps, where is the number of cycles in the graph.

In the constructed graph, there are only cycles, and each vertex is part of a cycle since exactly one edge leaves each vertex. Therefore, the maximum number of swaps occurs when the graph contains only one cycle.

Let’s calculate the number of such permutations: take some vertex at the beginning of our cycle—an edge from it can be directed to vertices since we can draw an edge to any vertex except ; let this be vertex . For drawing an edge from , vertices are available. Finally, we can proceed this way up to vertex , and from draw an edge back to to complete the cycle.

As a result, the number of such permutations is .

Problem 5

On the -th step, bubble sort places the maximum value from the modified slice at the end of the array. For iterations, on each iteration , the maximum value in the slice must be at index . This is possible only if the last element of the array is one. There are such permutations because the elements are in arbitrary order, and one is at the end of the array.

Problem 6

Proposed Algorithm

We solve the problem using the “divide and conquer” method. Suppose that after some iterations, the problem is being solved for the segment from to , and the results have already been calculated for and , where .

For each segment, the prefix and suffix sums have been calculated in sorted order—.

Since the results for and have already been computed, we are interested only in the sums that start in and end in .

Next, we need to iterate over the suffix sums of the first segment and select the prefix sums of the second so that their total sum does not exceed .

To do this, we can use two pointers: set the first pointer at the beginning of the suffix sums array of the segment , and the second pointer at the end of the prefix sums array .

Now we will decrease the second pointer as long as the sum .

When stops decreasing, we add to the result, since for all , . Thus, we have counted all sums that are possible when we have taken ; we move one step to the right and repeat the selection algorithm for .

Obviously, the results that start in and end in can be calculated in operations, since only increases and decreases because increases, so we need to decrease . Then, we need to somehow combine and , and to get the sums for .

Add to all elements of the value , and to all elements of add , since we need the sums on the whole segment.

Now we can simply merge these sums (in linear time) to obtain the required result.

Time Complexity Analysis

Initially, we have segments of length ; after pairwise merging, we have segments. Each iteration reduces the number of segments by a factor of 2; the number of iterations will not exceed . In each iteration, according to the proposed algorithm, operations are performed. The total complexity of the algorithm is .

Problem 7

The given array , after the manipulations performed on it, can be divided into three arrays:

  • : decreasing array
  • : increasing array
  • : decreasing, and every element of is greater than every element of

The minimal element of can be located at one of two positions:

  1. The last element of
  2. The first element of

The maximal element of can be located at one of two positions:

  1. The last element of
  2. The first element of

If we find the pair of indices ( and ) of the minimal and maximal elements of , then the problem can be solved by binary search on , , and .

To find the indices, we need to perform a binary search with —we obtain a monotonic sequence of zeros and ones. Then, using binary search, we find the index of the first one: can be either in or be the first element of .

We check whether is the first element of . To do this, it is necessary that the inequalities and hold.

  • If is the first element of , then .
  • Otherwise, is an element of . Then we perform a binary search with on to to find (where is the size of ).

To find , we need to perform a binary search with on to . Then we obtain:

  • : to —monotonically decreasing
  • : to —monotonically increasing
  • : to —monotonically decreasing

Thus, the solution to the problem is to perform three binary searches on , , and . The algorithm solves the problem in time.

Problem 8

Given that satisfies (strict inequality), and , then:

By induction, . Then we construct an array such that , where:

We will search for zero using binary search; accordingly, we solve the problem in time.

Problem 9

By Position

In the interval:

(denoted as ), there are closest elements by position.

We find the -th and -th order statistics and perform two Partition operations:

  1. On the entire array by the -th order statistic.
  2. On the subarray by the -th order statistic.

In , there will be elements that would be in those positions in the sorted array. This solution works in time since order statistics and Partition operations work in .

By Value

The elements closest to the median by value in the sorted array form a segment containing the median.

Let . We find the median, the -th, and the -th order statistics and perform partitioning with respect to them.

We will search for the segment boundaries in and .

In each iteration:

  • In the left and right segments, take the middle elements— and .
  • Partition the left segment by the -th and -th order statistics, and the right segment by the -th and -th.
  • Consider the segment :
    • If the inequality holds, then this segment needs to be shifted to the right (meaning the left boundary should be moved right of , and the right boundary right of ).
    • The opposite case is expressed similarly, with the inequality .

Thus, in each iteration, we will reduce the size of the segments by half until convergence.

The elements located between the right and left boundaries of the segment will be the desired result (partitioning was performed each time).

In each iteration, the number of operations is linear with respect to the length of the intervals, totaling:

The proposed algorithm solves the problem in time (taking into account finding the median and initial partitions).

Problem 10

a. At each step (from 0 to ), we will:

  • Remove elements from the end of s while a[l] ≥ a[c], where l is the last element in the stack, and c is the current index being considered.
  • Set a[-1] equal to negative infinity (-inf).
  • The last element in s after all operations will be the answer for c (we add it to the result array). If s is empty, there are no elements to the left that are less than a[c].
  • Add c to the stack s and proceed to the next step.

b. - (No content provided)

c. Given a matrix with rows and columns.

We will find —the nearest one above .

  • If , then .
  • If and , then .
  • Otherwise, .

That is, if , it implies there are no ones above .

We consider that each non-zero submatrix is defined by four boundaries.

We will attempt to find all possible lower boundaries of the desired largest zero submatrix—let’s iterate and assume it is row .

The desired submatrix touches a cell with a one or the boundary of matrix at the top.

Next, we iterate over all possible positions where the submatrix touches a cell with a one above. To do this, we go through all columns ; for each, we find the value and take it as the exclusive upper boundary.

Now that we have the upper boundary, we need to find the left and right boundaries.

To find the left boundary, we move along the columns to the left. If in the current column , where , the inequality holds, then is the exclusive left boundary.

Similarly, moving from to the right, we find the first such column for which .

We calculate the area: the left, right, and upper boundaries lie outside the rectangle, and the lower boundary is inside it. The area is:

The maximum among all computed areas is the required answer to the problem.

To ensure the algorithm has a complexity of , we will compute the left and right boundaries in the same way as we solved Problem 2.a (using a stack).

Problem 11

Let the weights of the items be . We define . If we receive a query with a weight greater than , we immediately answer (not possible / False). Also, for weight zero, we trivially answer (possible / True).

We create a table of size and initialize it with . For each element , we record which sums are possible to obtain, indicating in this table the maximum possible index that needs to be taken to achieve this sum on the segment from to .

For the first number, we can only obtain the sum :

This means that to obtain the weight on the segment ending at index , we must also choose index as the start of the segment.

We fill the second row as follows: if , then:

otherwise:

We fill all subsequent rows similarly, obtaining the following recurrence:

  • If , then:

  • If , then:

  • If , then:

Thus, we have dynamically filled the table in time—this is the preprocessing step.

To answer the original question (whether it is possible to obtain a subset of items within a given range), we check the condition . If it holds, then the answer is (possible); otherwise, (not possible). Therefore, we can answer each query in time.

Problem 12

If the array consists of one element, then the answer is that element. If it consists of two elements, then the longest palindromic subsequence is either the entire sequence (if both elements are equal) or one of the elements. If it consists of three elements, we need to compare the first and third elements: if they are equal, the answer is the entire sequence ; otherwise, the problem reduces to the case with two elements.

Let be the length of the longest palindromic subsequence in . The solution to the problem is found in .

We define the recursive formula:

  • If , then:

  • If , then:

  • If , then:

  • If , then:

By filling the table using this recurrence, we can solve the problem in time.

Problem 13

Let’s create a modified doubly-linked list as follows: besides two pointers we will store separately from the node some bit - it will determine which of the pointers to use to move to the next/previous element, and in the head of the list we will additionally store a pointer to the tail of the list, also in the tail a pointer to the head of the list.

Let’s expand the list: swap the head and tail pointers, invert the value - this way we have expanded the list in constant time, all other operations will be performed as before.

Problem 14

We will store the given table in a doubly-linked list. And we will also assume that we store some pointer to the original value stored in a cell of the table (by the condition the cell contents are not defined), for example $$$[0, 0]$$..

Then, for example, the table (and for the sake of example, we will also select the area from which rotation is possible):

And as a result of rotating the highlighted part by 180’, this table will have the following form:

And the bi-linked list for storing the initial table from the example will look like this:

🔁🔁🔁🔁🔁🔁🔁🔁

As a result, we will rotate some piece of the table as follows for :

  • Let’s exchange the pointers of the boundary cells of the chunk on the left and on the right
  • Also at the “beginning” of the chunk (top left) and at the “end” (bottom right), we will write a bit $$$b$$ that determines that there was a rotation

In the worst case, there are such boundary cells, so we claim that the rotation is realized in linear time. As a result, rotation operations of arbitrary rectangular pieces of the table can be accomplished in .

And the output of the whole table is possible in : as a usual traversal of such a list, but we will look whether in each cell that we traverse there is a bit defining the rotation:

  • if there isn’t, we traverse the list as usual—move to the next element with the next pointer
  • if there is, then we use the prev pointer to move to the next element

Problem 15

Since the problem is solved offline, we know all the deletion requests in advance.

Suppose we have an empty graph to which we can add an edge (let’s add the edge of the last query, penultimate query, and beyond). Then our problem is inverted and we try to solve the problem of counting connectivity components after adding any edge. Here we can also apply SNM - each connectivity component = one set in SNM (they are merged when adding edges). It turns out that the complexity of such a solution by using SNM is .

Problem 16

Since the problem is solved offline, we know all queries in advance.

To solve the problem, we use SNM (with path compression heuristics), where in each cell we store a reference to the unpainted cell nearest to the right (i.e. at the very beginning the cell points to itself). After painting the first subtrack, the cell before the start of the subtrack will point to the cell after the end of the subtrack.

We will process the queries in reverse order. So when we execute a query, we need to paint exactly certain cells in the segment . All other cells already contain their final color. To execute each query we will use SNM: find the leftmost unpainted cell inside the segment, paint it in color , and with a link move to the next unpainted “empty” cell to the right.

As a result, the problem will be solved in , where is the number of queries.

That is, in this solution we failed to achieve the asymptotics specified by the condition, I will wait for the analysis

Problem 17

Let’s assume that painting in white is a entry in a cell, and painting in black is a entry.

Then we have three types of queries

  1. Assigning to a cell.
  2. Assigning to cell .
  3. Searching for the th zero.

We will store in the vertices of the segment tree the number of zeros occurring in the corresponding sub segments of the array (tape). Let us construct such a segment tree for .

We will go down the segment tree starting from the root and move to the left or right descendant depending on which of the segments contains the -zero. That is, to understand which descendant to move to, you need to look at the value in the left descendant - if it is greater than or equal to , then move to the left descendant (its segment has at least zeros), otherwise move to the right descendant. Executed for .

A query with assignment or in our case means adding or decreasing the number of zeros we store in a certain vertex - that with the tree is executed for .

Problem 18

Let’s start a segment tree to store the tape. Let’s assume that coloring in white for a ribbon cell is assigning to the corresponding array element, coloring in black is assigning . And in each vertex, except for the left and right borders of the sub segments we will store

  • the number of consecutive black segments inside the segment (number of sections), for which the vertex is responsible - the sum of the number of sections of each subline (but if one subline ends at 1 and the other begins at 1, we will subtract one).
  • mark whether the data in the vertex is up to date.

Let’s fill such a tree for .

We will perform the value assignment operations in the same way as in the previous problem, but while we are going down the tree until the value is set in the leaf, we will mark all visited vertices as requiring an update.

For requests for the number of sections to actual sub-trunks, the answer will always be ready, and if the request falls on a sub-trunk that is marked as requiring an update, we will push the update (from top to bottom to the required sub-trunk) and thus count the number of sections. That is, all operations can be performed in .

Problem 19

We will create a tree for each axis—one for the axis and one for the axis—to enable quick minimum and maximum searches along both axes. In each node of the tree, we will store a reference to the corresponding node in the tree. This ensures synchronization between the trees during insertions and deletions.

Since trees allow us to quickly find minimum and maximum values, calculating the area of the minimal rectangle is straightforward:

where and .

Therefore, the complexity of insertion or deletion operations is .

The complexity of finding the area is .

Problem 20

We use the data structure for storing points on a plane from the previous problem; effectively, we “overlay” a grid on our polygon.

To determine whether a point lies within the rectangle, we conceptually draw an arbitrary line from the point (we simply search for the corresponding cell):

  • If the line does not intersect any of the sides, the point is outside.
  • If there are intersections with the sides of the polygon, the point is inside if the number of intersections is odd.

The query for one point in one cell is . For points and vertices, the total complexity is .

Problem 21

A set of keys is divided into three groups:

  1. keys up to
  2. keys in the interval
  3. keys after +\mathrm{k}$$ .

During the loop execution, the vertices before will be accessed at most times. The same is true for the vertices after , because by the condition the tree is balanced

Because of the peculiarities of calling on consecutive keys, we will visit each vertex from to at most a constant number of times, but only .

Thus we proved .