Блог пользователя Dogacel

Автор Dogacel, история, 14 месяцев назад, По-английски

Kotlin Heroes: Episode 12

First of all, I would like to start with sharing my slim template for kotlin competitive programming environment. If you would like to learn more, read the spoiler below. I wish I could have solved all, but unfortunately I was only able to figure out the solutions to the first 5 problems.

Local Kotlin Template

Problem A — Password Generator

Considering $$$a$$$, $$$b$$$ and $$$c$$$ is less than 10 and "no two adjacent characters in the password should be the same", we can construct a string by appending $$$a$$$ unique digits, $$$b$$$ unique upper case characters and $$$c$$$ unique lower case characters. As their upper bound is 10, we don't need to worry about characters becoming adjacent as our character pool is large enough.

Solution

Problem B — Showmatch

Assume all players are sorted by their score, $$$a$$$. It is safe to assume it as we are only changing each player's index for the sake of this proof. For any given player $$$i$$$, their best opponent is either $$$i-1$$$ or $$$i+1$$$. Because the array is in the sorted order, $$$|a_i - a_{i+2}|$$$ is always greater than $$$|a_i - a_{i+1}|$$$ and vice versa.

Therefore we need to sort the array $$$a$$$, check each tuple and make sure every player is matched to their best opponent.

Iterate over each pair, $$$(a_i, a_{i+1})$$$ make sure $$$|a_i - a_{i-1}| \geq |a_{i+1} - a_i|$$$ and $$$|a_{i+2} - a_{i+1}| \geq |a_{i+1} - a_i|$$$. This ensures everyone is matched to their best opponent.

Note: Make sure you start at $$$i=0$$$ and increase $$$i$$$ by 2 because you want to iterate over each matchup.

Solution

Problem C — Coin Game

In this game, the optimal solution for each player is to take the type of coin that has the most amount. So the player 1 will always pick the coints with the type that has the most amount and least amount, as player 2 will pick the middle one.

We could have brute-forced this solution but we have $$$q \leq 10^5$$$ queries and $$$|s| \leq 10^5$$$. Thus total operations we should make would be $$$q \times |s| \leq 10^{10}$$$ and it is not feasible.

So we should do a dynamic-programming solution to make sure each of our queries run in $$$O(1)$$$ constant time. We can do this by memorizing the number of coins given in each interval $$$[l, r]$$$. Let's say $$$dp_{gold}[i]$$$ represents the number of gold coins inside the interval $$$[0, i)$$$. We can calculate the number of coins between $$$l$$$ and $$$r$$$ by $$$f(l, r) = dp_{gold}[r + 1] - dp_{gold}[l]$$$.

Using this memoization, we should only calculate $$$dp_{gold}$$$, $$$dp_{silver}$$$ and $$$dp_{bronze}$$$ once during initialization. Later on we can calculate the number of coins given inside the interval $$$[l, r]$$$ in $$$O(1)$$$ time using the equation above.

Solution

Problem D — Uppercase or Lowercase?

In this problem, we have at most $$$500$$$ entries in our DB. We are also given at most $$$10$$$ queries. We would like to do a binary search to efficiently query those entries and $$$8 \leq log_{2}{500} \leq 9$$$. This gives us an extra query chance to figure out if capital letters are ordered first or last.

We can learn about this by querying the first entry. If it starts with a capital letter, we can assume capital letters are ordered first. If not, lowercase letters are ordered first. To emulate this behavior in our binary search algorithm, we can invert the first character's capitalization of each word if lex order is inverted. By doing this, our database will be ordered correctly.

Solution

Problem E

The optimal solution to this problem contains at most 2 sweeps. While sweeping from one direction to another, we can take all arrows that point at the sweeping direction that has $$$c_i \gt 0$$$. At some point $$$j$$$, we should stop and start sweeping for the opposite direction. There is no better solution with a 3rd sweep, because we have already took all arrows that has $$$c_i \gt 0$$$. However the optimal solution might consider only 1 sweep as well.

So the solution can be either,

  1. Only take all arrows that point to the right that has $$$c_i \gt 0$$$.
  2. Only take all arrows that point to the left that has $$$c_i \gt 0$$$.
  3. Find the constant $$$j$$$ so that, take all arrows that point to the right that has $$$c_i \gt 0$$$ and $$$i \lt j$$$. At the point $$$j$$$, there must be an arrow pointing left. Make a U-turn and take all left arrows that has $$$c_i \gt 0$$$.
  4. Find the constant $$$j$$$ so that, take all arrows that point to the left that has $$$c_i \gt 0$$$ and $$$i \gt j$$$. At the point $$$j$$$, there must be an arrow pointing right. Make a U-turn and take all right arrows that has $$$c_i \gt 0$$$.

For number 1 and 2, the solution is a simple $$$O(n)$$$. However number 3 or 4 requires $$$O(n^2)$$$ iterations without any memoization.

So let's create a dynamic-programming solution. I will only explain number 3, but it's logic is applicable to number 4 as well. Define $$$dp_{left}[i]$$$ as the sum of all arrows, such that $$$c_i \gt 0$$$, that point left between the interval $$$[0, i)$$$. Also define $$$dp_{right}[i]$$$ as the sum of all arrows, such that $$$c_i \gt 0$$$, that point right between the interval $$$[0, i)$$$.

So to find the optimal result by doing a left to right sweep first and later doing a right to left sweep, we can iterate over all $$$j$$$ in $$$O(n)$$$ time.


$$$

= \begin{cases} dp_{right}[j] + c_j + dp_{left}[j], & \text{if jth arrow is a left arrow,} \\ 0, & \text{else}. \end{cases}

$$$

We have calculated the total amount gained in the right sweep with $$$dp_{right}[j]$$$, we have added the cost of making the U turn with $$$c_j$$$ and finally we added the amount gained in the left sweep with $$$dp_{left}[j]$$$.

Solution
  • Проголосовать: нравится
  • +6
  • Проголосовать: не нравится

»
14 месяцев назад, скрыть # |
 
Проголосовать: нравится +3 Проголосовать: не нравится

Auto comment: topic has been updated by Dogacel (previous revision, new revision, compare).

»
14 месяцев назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

Auto comment: topic has been updated by Dogacel (previous revision, new revision, compare).

»
14 месяцев назад, скрыть # |
 
Проголосовать: нравится 0 Проголосовать: не нравится

I think your solution to E is overcomplicated. We can fix the rigthmost < or the leftmost > that will be taken. For example, for the < with index r answer will be c[r] + max(0,c[0]) + max(0,c[1]) + ... + max(0,c[r-1]).

  • »
    »
    14 месяцев назад, скрыть # ^ |
     
    Проголосовать: нравится 0 Проголосовать: не нравится

    I did not understand what you mean by "fix". I have visualized my approach as a "sweep". If you combine number 3 and 4 in my solution, the sum might be equal to what you said. However I believe it is easier to visualize in your head by thinking it as making a U-turn. I don't think less steps is necessarily less complicated.

    • »
      »
      »
      14 месяцев назад, скрыть # ^ |
       
      Проголосовать: нравится 0 Проголосовать: не нравится

      I mean let's assume that certain i is the last or the first index, that we will take. Let's consider the case that i is the first index we take, case that i is the last can be handled symmetrically. if i-th character is <, then we can take only i, because of our assumption, if it is >, we can take anything on the right of it, so simple greedy will work.