New Year Contest

for Andrey's friends

00

About the New Year Contest problems

On January 1, 2025, I held my first New Year Contest — more than 100 people took part in it! Below I’ll tell you what the problems were and how they could be solved. If you want to join this year, registration is open to everyone: 2026.andgein.ru!

There were six problems in total, each of which in turn consisted of several levels. All levels share the same statement but differ in the input parameters for the problem. Your goal is to obtain the answer to the problem for the given parameters in any way you like. To move on to the next level, you need to solve the current one; however, if you can’t solve it, you may skip that level. The further you go, the harder the levels become, and that was the main idea of the contest — the first levels are accessible to absolutely everyone, but to reach the end you’ll have to find and apply more advanced ideas.

In most problems, the levels were meant to be solved automatically, so the contest provided a special API for fetching levels and submitting answers.

Problems

  1. "A + B"
  2. "Sum of Squares"
  3. "Explain the Word"
  4. "YAML Minimization"
  5. "Match Them All"
  6. "Shaken, not Stirred"
01 a-plus-b

Problem "A + B", 200 levels

A classic of programming contests! I couldn’t help starting with the problem that opens the practice rounds of almost every contest. Besides, it was a great way to introduce participants to the competition format and the system itself. The problem starts with trivial levels — you just need to add two integers. Together with the fact that the problem has 200 levels, the participants also learn about the API that helps them fetch levels and submit answers automatically. I even provided code to work with it:

#!/usr/bin/env python3
import requests, time

URL = "$BASE_URL$/api/tasks/a-plus-b"
HEADERS = {"Token": "$TOKEN$"}

# Solve as many levels as we can!
while True:
    # Fetch the current level...
    task = requests.get(URL, headers=HEADERS).json()
    # You don’t need to parse the task text to find the numbers to add:
    # input data for tasks will always be in task["parameters"].
    # For example, in this case it looks like {"A": "192836", "B": "837293"}.
    answer = int(task["parameters"]["A"]) + int(task["parameters"]["B"])

    # Submit the answer...
    r = requests.post(URL, headers=HEADERS, json={"level": task["current_level"], "answer": str(answer)}).json()
    # If the answer is incorrect, it’s better to exit rather than keep resubmitting it.
    if not r["is_correct"]:
        print(r["checker_output"])
        break

    print("Great, another level completed:", task["current_level"])

    # Take a short break...
    time.sleep(0.1)

Just by running this code or rewriting it in your favorite programming language, you can pass the first 40+ levels. Not bad!

Further on, though, this solution stops working, because the next level asks you to add 1111010010100000001101010₂ and 11010010010110001111101110₂... That’s how we realize that in this problem the inputs may be numbers written in binary. We fix the code accordingly and pass another few dozen levels. At some point the levels start featuring very large numbers (75205354748480883989811876184800672313572651799835041211739711421785781432735 + 99147694418509409465418140307673055197104449558121420831908040349553739802151), which may become a problem in some programming languages. We overcome that difficulty as well!

As you progress through the levels, you will also encounter:

  1. Powers: 20² + 25²
  2. Real numbers: 0.1 + 0.2
  3. Irrational constants: ℯ⁵ + 𝜋²
  4. And even complex constants: 𝜋 + 𝑖¹¹²

In levels with non-integer numbers, a third problem parameter appears: the number of digits after the decimal point with which you must output the answer.

Solution idea

It’s enough to correctly handle all the scenarios that appear among the 200 levels. Of course, you can do this in any programming language. In Python specifically, I recommend using the built-in type decimal.Decimal, and taking sufficiently precise values of 𝜋 and ℯ from the mpmath module.

02
lagrange

Problem "Sum of Squares", 200 levels

The problem starts with a level that asks you to decompose 2025 as a sum of squares of integers. How convenient that 2025 is exactly 45²! On the second level you’re asked to decompose 20252025 as a sum of squares (which, as it’s not hard to guess, is 4500² + 45²), but then things start to get interesting...

In the general case you are asked to represent a natural number as a sum of at most four squares. In some levels the requirement is stricter: the number must be represented as a sum of at most two or three squares.

On the early levels the numbers are small enough that any brute-force solution works, but gradually the numbers get longer and the solution gets harder. For example, try to decompose as a sum of squares 72320111127424371937031538808252981641829647135466806132064340220652523294744143502280637801129499994!

If you cannot decompose the number into the required number of squares, you must answer “NO”.

Solution idea

If you google it ask an LLM, you’ll find out that there is Lagrange’s four-square theorem from as early as the 18th century, which states that any natural number can be written as a sum of four squares. But how do we find these numbers? And what do we do if we need two or three squares?

The answer to the second question is fairly simple: a natural number can be written as a sum of two squares if and only if, in its prime factorization, there is no factor p ^ k such that p = 3 (mod 4) and k is odd. Why? Because.

In turn, a number can be represented as a sum of three squares for all numbers except those of the form 4 ^ a × (8b + 7). Because of Legendre’s three-square theorem. In short, everything has already been proven for us.

Thus, checking whether an answer exists is quite easy. What remains is to actually find it. Although many participants wrote their own solutions, I expected this to be a problem of finding existing, well-optimized code. The fact is that most solutions on the internet are brute-force ones and are not suitable for large numbers, but a good solution does exist.

One such solution can be found here (with comments in Korean!). The theoretical foundation of the algorithm is described in a 1986 paper by Rabin and Shallit.

Another method is described in a note by Michael Barra and Dario Alpern. Their code can be found in the latter’s GitHub repository.

03
depicting

Problem "Explain the Word", 40 levels

The problem is simple: you need to send a picture which, when sent to ChatGPT with the prompt "What is depicted in this picture? Answer in one word strictly, nothing more", produces the given English word.

It all starts harmlessly and simply — with words like “Christmas” and “Winter”, but in the middle you get “Nuance”, and closer to the end — “Antidisestablishmentarianism”. What does “Antidisestablishmentarianism” look like?

Solution idea

To solve this problem, it’s enough to write the desired word in large bold letters on an image, preferably on a white background. Here’s an approximate solution using ImageMagick:


magick -pointsize 50 label:{word} result.png
        
04
minify-yaml

Problem "YAML Minimization", 137 levels

You are given a YAML document and a number N. Your task is to send back a YAML document equivalent to the given one, whose length does not exceed N.

Solution idea

If you’re not very familiar with the YAML format, it’s worth starting with the documentation or at least with the Wikipedia page. In the solution itself you had to carefully implement two ideas.

First, you had to take some YAML serializer and start tweaking it so that it produced shorter documents. For example, in many cases strings can be printed without quotes, null can be replaced by ~ (explanation), spaces between the colon and the value can (in most cases) be removed, long string constants should not be wrapped, and so on.

Second, starting from the 18th test, the input data contains many repeated fragments. They should be replaced by YAML anchors of the form &a and *a, since this lets you avoid duplicating large chunks of the document. The anchor names must be chosen as short as possible, while respecting the list of allowed characters.

The idea with anchors is developed further in later tests, where it becomes profitable to replace not only whole blocks, but also individual lines or numbers with anchors. For example,


calendar: -5810438722.305
id003:
  education: -5810438722.305
  id007: -5810438722.305
        

can be turned into


{calendar: &0 -5810438722.305,id003:{education: *0,id007: *0}}
05
regex

Problem "Match them All", 23 levels

You are given two sets of strings (there is nothing unusual in the strings themselves: mostly Latin letters, digits and punctuation). Your task is to write a sufficiently short regular expression that matches every string from the first set and matches none of the strings from the second set.

Solution idea

The main idea is to generate lots of small regular-expression fragments that match some strings in the first set and match none of the strings in the second set. Then you combine some number of such fragments with |.

The fragments can be different: for example, of the form "ab.*c", "a\db", or "[a-e].\w".

An example of how to search for the best fragments and quickly discard bad ones can be found here.

The reference solution also used a few extra tricks and optimizations. For example, at the very end you can try removing some character from the answer and checking that the regular expression still works. Another idea is that it is sometimes advantageous to swap the sets before searching for the regular expression and then wrap it in a big negative lookahead. For some string sets you could also build a case-insensitive regular expression and then add (?i) at its start.

06
shuffled-image

Problem "Shaken, not stirred", 12 levels

I encrypted a picture. To do this, I first split it into squares of the same size (for example, 16 by 16 pixels), and then shuffled the pixels inside each square. For simplicity, I used the same permutation of pixels inside every square. Your task is to decrypt the picture (the permutation used is, of course, unknown!) and answer a question about this picture. For example, “how many windows does the plane have”?

Shuffled image

Solution idea

It may seem that without knowing the permutation it’s impossible to reconstruct the picture. And brute force won’t take you far either, as there are too many possibilities... However, in reality you can reconstruct the picture quite accurately!

To do this, we use the simulated annealing algorithm. The idea is that, starting from a random state, you can arrive at an optimal one if you perform random moves and watch how the picture changes. In our specific case, a random move is swapping some two pixels in every square of the original image. All that’s left is to evaluate after such a swap whether the picture has become “better” or “worse”, and by how much.

To answer this question, we need to define a “score function” for the picture. The more realistic the picture looks, the higher the value of this function should be, and the more it resembles a soup of shuffled pixels, the lower. Note that in the correctly reconstructed picture, similar pixels are likely to stand next to each other. For example, pixels of the blue sky stand next to other blue sky pixels, and pixels of the yellow sun stand next to other yellow sun pixels. Therefore, the score function can be computed as the sum of differences between neighboring pixel values. All that remains is to learn how to quickly recompute this function after swapping a pair of pixels in every square :-)

Restored image

See also the solution by Borya Minaev.

07

Like it?

The New Year Contest 2026 will take place on January 1, and you can still register! Join us at 2026.andgein.ru.