Wednesday, July 24, 2024

A 39 vs 17 week

Codeforces Round 959 sponsored by NEAR was the main event of last week (problems, results, top 5 on the left, analysis). AWTF participants have probably already returned home and were ready to fill the top five places in this round, but Egor has managed to solve everything and squeeze in the middle of their party. Congratulations to Egor on doing this and to tourist on winning the round!

In my previous summary, I have mentioned two problems. The first one came from AWTF: you are given n<=250000 slimes on a number line, each with weight 1. You need to choose k of them, all others will be removed. Then, they will start moving: at each moment in time, every slime moves with velocity r-l, where r is the total weight of all slimes to the right of it, and l is the total weight of all slimes to the left of it. When r-l is positive, it moves to the right, and when it is negative, it moves to the left. Since this rule pushes the slimes towards each other, sometimes they will meet. When two or more slimes meet, they merge into one slime with weight equal to their total weight, which continues to move according to the above rule. Eventually, all slimes will merge into one stationary slime, suppose this happens at time t. What is the maximum value of t, given that you can choose the k slimes to use freely?

Even though the problem does not ask for it, it is actually super helpful to think where will the final big slime be located. For me, this would have probably been the hardest part of solving this problem. Why should one think about the final position if the problem only asks about the final time?..

After studying a few examples, one can notice that the final position is always equal to the average of the starting positions of the slimes. Having noticed this, it is relatively easy to prove: consider the function sum(ai*wi) where ai is the position of the i-th slime, and wi is its weight, and consider two slimes (ai,wi) and (aj,wj). The first one contributes -wi to the velocity of the second one, while the second one contributes wj to the velocity of the first one. Therefore together they contribute -wi*wj+wj*wi=0 to the velocity of value sum(ai*wi), therefore that sum stays constant. And it also does not change when two slimes merge, therefore it is always constant and has the same value for the final big slime.

Now is the time for the next cool observation, this one is a bit more logical though. Consider the last two slimes that merge. They split the original slimes into two parts. What happens to the weighted averages of the positions of those two parts sum(ai*wi)/sum(wi)? By the same argument as above, influences within each of the parts on that average cancel out. The influences of the parts on each other do not cancel out though, but we can easily compute them and find out that those two averages are moving towards each other with constant velocity equal to the total weight, in other words k.

Therefore if we know which two parts form the two final slimes we can find the answer using a simple formula: (sum(ai*wi)/sum(wi)-sum(aj*wj)/sum(wj))/k, where j iterates over the slimes in the first part, and i iterates over the slimes in the second part.

And here comes the final leap of faith, also quite logical: the answer can actually be found by finding the maximum of that amount over all ways to choose a prefix and a suffix as the two parts. This can be proven by noticing that the difference between averages starts decreasing slower than k if some slimes merge across the part boundary, therefore the number we compute is both a lower bound and an upper bound on the actual answer.

We have not yet dealt to the fact that we have to choose k slimes out of n, but seeing the above formula it is pretty clear that we should simply take a prefix of all available slimes for the sum over j, and a suffix of all available slimes for the sum over i. Now all pieces of the puzzle fit together very well, and we have a full solution.

The second problem I mentioned came from Universal Cup: you are given a string of length n<=500000. We choose one its arbitrary non-empty substring and erase it from this string, in other words we concatenate a prefix and a suffix of this string with total length less than n. There are n*(n+1)/2 ways to do it, but some of them may lead to equal strings. How many distinct strings can we get?

A natural question to ask is: how can two such strings be the same? If we align two different ways to erase a substring of the same length that lead to the same result, we get the following two representations of the same string:

abcd=aebd

where in one case we delete the substring c, and in the other case we delete the substring e to obtain the result abd. We can notice that such pairs of equal prefix+suffix concatenations are in a 1:1 correspondence with pairs of equal substrings within the string (two occurrences of the substring b in the above example). It is not true though that our answer is simply the number of distinct substrings, as we have merely proven that the sum of c*(c-1)/2 over all repeat quantities c of equal substrings is the same as the sum of d*(d-1)/2 over all repeat quantities d of equal prefix+suffix, but that does not mean that the sum of c is the same as the sum of d.

However, this makes one think that probably the suffix data structures, such as the suffix tree, array or automaton, will help solve this problem in the same way they help count the number of distinct substrings. This turns out to be a false path, as the actual solution is much simpler!

Consider the middle part of the above equation (bc=eb), and let us write out an example with longer strings for clarity:

abacaba
abacaba

We can notice that the following are also valid ways to obtain the same string:

abacaba
abacaba
abacaba
abacaba

So the structure here is simpler than in counting distinct substrings. In order to count only distinct prefix+suffix strings, let us count only canonical respresentations, for example those where the prefix is the longest. The criteria for when we cannot make the prefix even longer is evident from the above example: the next character after the prefix (the one that is removed) must be different from the first character of the suffix, if any. Therefore the answer is simply equal to the number of pairs of characters in the given string that differ, plus n to account for the representations where the suffix is completely empty. 

Thanks for reading, and check back next week!

Sunday, July 14, 2024

An ecnerwala week

AWTF24 was the main event of this week. I have mentioned its results in the previous post, so I want to use this one to discuss its problems briefly.

Problems A and B both had short solutions and were quick to implement, but required one to come up with a beautiful idea somehow. When Riku was explaining their solutions during the broadcast, I was amazed but could not understand how to come up with them :) One way to do it, at least for problem A, was to actually start by assuming the problem is beautiful, and to try coming up with some beautiful lower or upper bound for the answer, which can turn out to be the answer.

To test if you can walk this path, here is the problem statement: you are given n<=250000 slimes on a number line, each with weight 1. You need to choose k of them, all others will be removed. Then, they will start moving: at each moment in time, every slime moves with velocity r-l, where r is the total weight of all slimes to the right of it, and l is the total weight of all slimes to the left of it. When r-l is positive, it moves to the right, and when it is negative, it moves to the left. Since this rule pushes the slimes towards each other, sometimes they will meet. When two or more slimes meet, they merge into one slime with weight equal to their total weight, which continues to move according to the above rule. Eventually, all slimes will merge into one stationary slime, suppose this happens at time t. What is the maximum value of t, given that you can choose the k slimes to use freely?

Problem D turned out to be equivalent to an old Codeforces problem applied to the inverse permutation. Most of this week's finalists have participated in that round or upsolved it, so it was not too unfair. The top two contestants ecnerwala and zhoukangyang did solve the Codeforces problem back in 2022, but did not remember it, and implemented the solution to D from scratch (even though of course having solved the old problem might have helped come up with the correct idea here). ksun48 and heno239 in places 3 and 4 did copy-paste their code from 2022.

Problems C and E involved a bit more code and effort to figure out all details, but one could make gradual progress towards a solution when solving them, instead of having to pull a beautiful idea out of thin air. Were I actually participating in this round, I would most likely spend the most time on, and maybe even solve those two problems.

Overall, this was a very nice round, and I'm looking forward to more AGCs in 2024 to try my hand at more amazing problems!

On the next day after the AWTF, the 3rd Universal Cup Stage 4: Hongō took place (problems, results, top 5 on the left). 8 out of 9 participants from the first three teams (congrats!), which coincidentally are also the first three teams in the season ranking, were in Tokyo, so Riku and Makoto have organized an onsite version of this stage at the AtCoder office. Solving a 5-hour contest with your team in person instead of online is already much more fun, but having other teams in the room and discussing the problems with them right after the round is even better. I guess I'm still yearning for the Open Cup days :)

I was solving this round together with Mikhail and Makoto. Thanks a lot Makoto for briefly coming out of retirement to participate, it was great fun solving this round together, and I can confirm that you're still very strong! Maybe we can have another go next year.

Problem N was not very difficult (I spent at least half an hour without any success, explained the problem to Makoto and he solved it almost immediately), but still enjoyable: you are given a string of length n<=500000. We choose one its arbitrary non-empty substring and erase it from this string, in other words we concatenate a prefix and a suffix of this string with total length less than n. There are n*(n+1)/2 ways to do it, but some of them may lead to equal strings. How many distinct strings can we get?

Thanks for reading, and check back next week.

Saturday, July 13, 2024

AWTF24 contest

AtCoder World Tour Finals 2024 took place today (problems, results, top 5 on the left, broadcast recordinganalysis). This was my first 5-hour commentating experience, and I enjoyed it a lot! How did it look from your side? Please share your improvement suggestions, just for the remote chance that I do not qualify again :)

This time the contestants had an opportunity to share their thoughts on stream (similar to the "confession booth" concept from some chess tournaments recently), and while not everybody used it, it was great fun and great insight to listen to those who did (for example; did somebody maybe gather all timestamps?). I hope this practice gets expanded and improved at future contests!

ecnerwala also tried to share his thoughts before the contest even started, but unfortunately the stream had not started as well at that point and therefore his words were not recorded. Nevertheless, maybe this helped him get into the right mood and solve problem E quickly, which was key to his win. Congratulations to him and to zhoukangyang and ksun48 who also won prizes!

Thanks for reading, and check back tomorrow for this week's summary.

Thursday, July 11, 2024

AWTF24 pre-contest

On Wednesday, the contestants were gathering in the hotel. The contestants from Europe and America hat some very long flights behind them, so there was not much appetite for activities. Therefore we played some board games in the hotel lobby in between short excursions to get some Japanese food. We did not actually meet most of the contestants from Asia — maybe the reason was that they actually had more energy for exploring Tokyo and did not hang around in the hotel :)

The games of choice (well, those were the only ones I brought so there was not that much choice...) were Cascadia and (Level 1 H-Group) Hanabi. It turns out that the synergies of the H-Group conventions are not so obvious at level 1, so probably next time we introduce somebody to them we should start at least with level 3.

We also got to witness the AtCoder admins printing the logos on the official t-shirts, as it turned out that the shop where one can print arbitrary content on a t-shirt in a self-service manner happened to be on the lower floors of the hotel building. Even though this is not much different from a normal printer, seeing how one can slightly adjust the image and then get an actual t-shirt with this image in a couple of minutes was quite impressive.

Today was a free day for the contestants, who have ventured a bit more into the city having rested from their travels. It was still funny with the timezone differences and jetlag, as the same meal was breakfast for me, lunch for the locals, and dinner for the contestants from America. Some contestants warmed up their problem solving capabilities by doing escape rooms, while others opted for actually solving old competitive programming problems for some last-ditch practice.

Tomorrow is the big day! The overall setup is similar to the last year, but with just one contest: 5 problems for 5 hours, the penalty time is computed as the time of the last successful submission (not the usual ICPC sum) plus 5 minutes for each incorrect submission. You can find more details in Riku's post. And of course tune in to see my and Riku's commentary on the live broadcast which will start at the same time as the contest, 12:30 Tokyo time, and last for 5 hours.

All 12 finalists are very strong, so it is hard to predict who will come out on top. zhoukangyang won 4 out of the last 6 AGCs, tourist has a lot of experience winning those contests, and jiangly has won the AWTF last year  — I guess we can keep an eye for those three, but anything can happen really.

Thanks for reading, and tune in tomorrow!

UPD: the live broadcast link has been updated.

Tuesday, July 9, 2024

A 熱中症予防 week

There were no contests that I'd like to mention last week, so I can get straight to the new format of this blog for the coming week: a travel blog! I am going to the AtCoder World Tour Finals 2024 in Tokyo. I did not manage to qualify this time, placing 14th when I needed to be in top 12, so I am going as a spectator and as a co-commentator for the stream, together with Riku, the main admin of AtCoder.

For a second year running, Tokyo welcomes the participants with an extreme weather warning in Japanese, this time for extreme heat. Please all take care, and focus on playing board games in the air conditioned hotel!

Speaking for 5 hours while 12 contestants are facing, to put it mildly, challenging problems is also not a walk in the park. Please help us by suggesting topics that we should discuss or things we should do on stream in comments!

In my previous summary, I have mentioned a Universal Cup problem: first, we draw the vertices of a regular n-gon (n<=10000) on the plane. Then we repeat the following process m times (m<=30000): take any 3 already drawn points (either the original n-gon vertices, or ones drawn during the previous steps) Ai, Bi, Ci, and draw the point Ai+Bi-Ci (the fourth vertex of a parallelogram). Finally, we need to handle multiple queries of the form: given k drawn points (the sum of k over all queries <=30000), do they form the vertices of a regular k-gon in some order?

We can get points that are really close to a regular k-gon but not exactly one in this problem, and no feasible floating point precision is enough. Therefore we need to solve it using only integer computations. Nevertheless, let us first consider how a floating-point solution might work.

We can imagine that the points lie on the complex plane, and the initial n points are ei/n. Drawing a new point corresponds to computing Ai+Bi-Ci using the complex number arithmetic. There are many ways to check if k computed points form a regular k-gon, here is one: we need to check that the k points, in some order, can be represented as x, xy, xy2, ..., xyk-1, where y is such that yk=1 and no smaller power of y is equal to 1. Note that this order does not have to be the clockwise/counter-clockwise order of the vertices: multiplying by y can also represent a jump by any coprime number of edges, and this criterion will still be valid. Also note that we can actually pick any of the vertices as x and such y will exist, moreover there will be φ(k) different values of y that work for each vertex. So one way to find y is to just try a few other vertices z of the polygon, let y=z/x, and check if the criterion is satisfied. Since φ(k) is not too small compared to k, we should find a valid y after a few attempts, let's say at most 50. Of course, we could've just said that y=ei/k, but you will see below that the y=z/x approach leads to an interesting question that I want to discuss.

If we denote the "base" point as u=e2π/n, then all other initial points are powers of u, all computed points are polynomials of u, and the checks we are making boil down to whether a certain polynomial of u with integer coefficients is equal to 0 or not (even though we also use division, checking if poly1(u)/poly2(u)=poly3(u)/poly4(u) is the same as checking if poly1(u)*poly4(u)-poly2(u)*poly3(u)=0). We could try to maintain said polynomials with integer coefficients, but since the degrees would be on the order of n, and the coefficients themselves could be huge, this is not really feasible within the time limit. 

Here comes the key idea: instead of complex numbers and u=e2π/n, let us do the same computations using integers modulo a prime p and using such u that the order of u modulo p is equal to n. Such u exists if and only if p=sn+1 for some s, so we can search for a random big prime number of this form, which can be found quickly since all arithmetic progressions with coprime first element and difference have a lot of prime numbers.

This actually works very nicely and allowed us to get this problem accepted. However, why does this actually work? The order of u is the same in our two models (complex numbers and modulo p), so every polynomial of the form ut-1 is equal to 0 in both or in neither model. However, this does not guarantee the same for an arbitrary polynomial with integer coefficients, or does it?

Of course it is not true for an arbitrary polynomial. For example, the polynomial p is equal to 0 modulo p, but not equal to 0 in complex numbers. However, we can deal with this by picking the modulo p at random, and potentially also checking several moduli in case the judges actually create testcases against many potential moduli. So the real question is: are there polynomials for which being equal to 0 differs between the complex numbers and computations modulo p for all or a significant fraction of all possible values of p?

Here we need to bring in some maths. When two polynomials with rational coefficients are equal to 0 for the given u their greatest common divisor also has rational coefficients and is also equal to 0 for the given u, which means that there must exist a minimal polynomial such that a polynomial with rational coefficients is equal to 0 for the given u if an only if the minimal polynomial divides it. Such minimal polynomial for our u is called the n-th cyclotomic polynomial Φn(u).

Now, consider the equality un-1=Φn(u)*gn(u) (where gn(u) is just the result of dividing un-1 by Φn(u)). This equality is true in rational numbers, so it is also true modulo p where there is no division by p in it, so for almost all p. The left-hand side is 0 modulo p because of our choice of u, so either Φn(u) or gn(u) must be 0. However, from the structure of the cyclotomic polynomials we know that gn(u) is a product of cyclotomic polynomials of smaller order, so if it was equal to 0, it would mean that the identity ut-1=0 would hold for some t<n, which contradicts our choice of u. So we know that Φn(u)=0 modulo p, which means that every polynomial with integer coefficients that is equal to 0 for the given complex u will also be equal to 0 for the given u modulo p. So we have proven one of the two required implications.

Now let us tackle the the opposite implication. Consider a polynomial h(u) with integer coefficients that is equal to 0 for all or a significant fraction of all possible values of p (with the corresponding u). If Φn(u) divides this polynomial (as a polynomial with rational coefficients), then it is also equal to 0 for the given complex u, as needed. If Φn(u) does not divide it, then we can find the greatest common divisor of Φn(u) and h(u), again doing computations using polynomials with rational coefficients. Since Φn(u) is irreducible over polynomials with rational coefficients, this greatest common divisor will be 1, so we have 1=Φn(u)*a(u)+h(u)*b(u). The right side involves a finite number of different integers in denominators, so this equality will also hold modulo p for all p except those dividing one of the denominators, in other words for almost all p. But since both Φn(u) and h(u) are equal to 0 for all or a significant fraction of all possible values of p, this means that 1 is equal to 0 for all or a significant fraction of all possible values of p, which is a contradiction. Therefore we have also proven the opposite implication and this solution does in fact work.

There are still a few things I do not fully understand about this setup. One is the following: it turns out that when n is odd, we can actually construct a regular 2n-gon (roughly speaking using the fact that -1 helps generate the other n points; there was such example in the samples for n=3, k=6), so k does not have to divide n. In this case, the number y that we find as part of solving the problem must have order 2n modulo p. However, note that in general it is not even guaranteed that there is any number with order 2n modulo p, as we only choose p in such a way that there is a number with order n. Since we compute y=z/x, we can do this computation for any p where we can compute z and x. So it seems that the above also proves that for almost all primes p if there is a number of odd order n modulo p, there is also a number of order 2n modulo p. This observation is in fact true for a straightforward reason: almost all primes are odd, so there is an even number p-1 of nonzero remainders, therefore there is a number of order 2, and we can multiply the number of odd order n by the number of order 2 to get a number of order 2n. Still, I can't get rid of the feeling that I might be missing something here. Any comments?

The second thing I don't fully understand is whether we truly need a full understanding of the structure of cyclotomic polynomials to prove that Φn(u)=0 modulo p. It feels that maybe there is an easier way to explain this that does not require so much knowledge?

Thanks for reading, and check back for more AWTF updates!

Monday, June 10, 2024

A code week

Universal Cup started its 3rd season this Saturday with Stage 1: St Petersburg (problems, results, top 5 on the left, analysis). Team 03 Slimes was in the lead for a long time, then paused a bit and gave everyone a chance to catch up, only to solve 3 more problems in the last hour and claim the clear first place. And all that even though our team stole one of their usual team members and they had to participate with just 2. Well done!

Solving problem B was very satisfying, and I think describing its solution next week will help me understand the underlying math better, so here is the problem statement: first, we draw the vertices of a regular n-gon (n<=10000) on the plane. Then we repeat the following process m times (m<=30000): take any 3 already drawn points (either the original n-gon vertices, or ones drawn during the previous steps) Ai, Bi, Ci, and draw the point Ai+Bi-Ci (the fourth vertex of a parallelogram). Finally, we need to handle multiple queries of the form: given k drawn points (the sum of k over all queries <=30000), do they form the vertices of a regular k-gon in some order?

Initially the problem seems quite straightforward as we can just simulate the process and check the required condition using some simple geometric formulas. However, it turns out that we can get points that are really close to a regular k-gon but not exactly one in this problem, and no feasible floating point precision is enough. So how to solve this using only integer computations?

Codeforces Global Round 26 wrapped up the week on Sunday (problems, results, top 5 on the left, analysis). One of the highlights of the round was an amazing successful experiment, but there was also a pretty close fight for the first place, to the point that jiangly needed just one successful hack to win, which he unsuccessfully attempted. I wanted to check if he had a chance, in other words if there were any failed system tests in jiangly's room, but I could not find a way to find which of the 646 available rooms is it. Does anybody know how to find the room of another contestant? Apparently this link was staring me in the face, it is shown at the top of the user's submission history, which appears after a double-click in the corresponding standings cell. And it turns out that there were no failed system tests in jiangly's room, which means that his only chance for the required +100 could lie in some anti-hash tech.

In any case, tourist held on to his first place, and increased the gap at the top of the rating list. Interestingly, the top 3 in this round coincided with the top 3 by rating after Benq did find his successful hack. Congratulations to tourist, jiangly and Benq!

Thanks for reading, and check back next week.

Friday, May 24, 2024

A return@week

Kotlin Heroes Episode 10 on Codeforces was the main event of last week (problems, results, top 5 on the left, analysis). Only solutions in Kotlin programming language could be submitted. Long gone are the days of IDEA's Java-to-Kotlin converter, at least nobody in the top 20 seems to use it, and I think the Kotlin Heroes series is quite a success in introducing people to Kotlin and getting them to like it. At the same time, Kotlin knowledge is still not at 100% in the community, so other translation tools are on the rise :)

The top 2 were the same as in the previous edition, and they were the only participants to solve all problems. However, this time arvindf232 was faster and earned a well-deserved victory. Congratulations to arvindf232 and tourist on the great performance!

Tourist's wrong submissions on E and F have seriously damaged his winning chances. The wrong submission on E (link, compare to correct) stems from a wrong understanding of an advanced language feature, return@repeat, which turned out to mean continue and not break :) And the wrong submission on F (link, compare to correct) stems from not using a prewritten code library, more specifically a modint class. The winner's submission to the same problem does use prewritten code to deal with modular arithmetics, but interestingly it is not done through operator overloading, but rather through defining new infix operations that also refer to a global variable, leading to weird-looking code like ret[paths] = ret[paths] mp (count mm FACT.choose(options, k-1)), where "mp" and "mm" stand for modular addition and multiplication. arvindf232 or others reading this, what is the reason for this approach?

Thanks for reading, and check back next week!

Wednesday, May 15, 2024

A busy beaver week

The final round of the MIT Informatics Tournament "M(IT)^2" 2024 Spring Invitational took place in Boston last week (problems, results are not published yet, top 5 on the left, online mirror results). Even though the competition was open to everybody who is willing to travel to Boston, the top 4 places were occupied by those who have won ICPC gold medals for the MIT team in the past :) Adam Gąsienica-Samek in the fifth place, to the best of my knowledge, is still a high school student in Warsaw (even though the last name does bring some ICPC memories; is it a coincidence or are they related?). Maybe he will win ICPC gold medals for MIT in the future :) Congratulations to the winners, and also to the organizers! Looking at how most of them are not graduating for at least a couple more years, I hope for more tournaments to come.

Thanks for the quick reading, and check back next week.

Sunday, May 5, 2024

A notorious week

Codeforces Round 942 was the first event of this week (problems, results, top 5 on the left, analysis). tourist has returned to the top of the scoreboard, and also to the top of the rating list — congratulations! It is also great to see that jqdai0815 keeps participating actively and getting great results after going for a long break during the pandemic. Who knows, maybe even I still have a chance to return to the top 10 :)

On Saturday, we hosted the online mirror of the Helvetic Coding Contest 2024 (problems, results, top 5 on the left, onsite results, analysis). This is originally an onsite competition that took place in Lausanne in April, it is ran completely by a group of student volunteers at the EPFL, who take care of all tasks such as finding sponsors, advertising the event, planning the onsite events, setting up the onsite competition environment, organizing meals, and of course preparing the problems. Seeing this volunteer-driven contest appear out of nowhere after a five-year hiatus really inspires and makes me proud of the competitive programming community. Therefore I have submitted two problems this year (A and D), I hope you liked them!

But I also need to mention that this year one of the problems unfortunately was the same as Problem 5 of the November Lunchtime 2021 at Codechef. As you can see, there were quite a few competitors that took part both in that round and in this week's mirror, and there could be much more who have upsolved it or heard about it from a friend, which of course made the contest worse, even though it was unrated. Below I am trying to share my understanding of what happened, but note that my goal is not to absolve myself from the responsibility — I think it was a failure, and I apologize for it.

As it is often the case, the reason for this seems to be bad communication along a chain of people operating with good intent. The original author (by the way, nice problem!) clearly knew that this problem was used for the Lunchtime when sending it to one of the HC2 organizers, but expected the HC2 organizers to make their own judgement about how appropriate it is to use it in the onsite event, they likely did not even know that a mirror might happen.

But then as the information about this problem propagated from one HC2 organizer to another through a couple of more hops, this fact was lost, with various people thinking that this problem was only used for a private contest with a few participants, or that this problem was prepared but rejected and not used at all in the past.

What probably made the matter worse is that different people have different perceptions of what is OK (should we never give the same problem to two contests? Or maybe it is OK if the set of participants is not intersecting and it was not available publicly after the first one? Or maybe it is OK if the first occurrence happened long ago? Or maybe it is OK if the second round is unrated?), and this perception affects what information about the problem they decide to communicate to other people.

Moreover, people often expect other people to have the same perception of the situation, and therefore treat the lack of communication as information as well. As a result, the people organizing the mirror (such as myself) did not try to figure out more information about the origins of this problem even though we knew from Polygon that it was prepared a bit earlier, assuming that other organizers who are closer to the beginning of this communication chain know the problem better and therefore are in a better position to judge if it is appropriate to use, so if they say nothing, all is good. But this line of reasoning fails to account for the fact that the people closer to the beginning of the communication chain have a different context (might not even be aware that there's a public mirror, for example) and different perceptions of what is OK.

So here is my takeaway from all this: more communication is always better when preparing a contest! I will try to keep this in mind when preparing future rounds, hopefully including the Helvetic Coding Contest 2025.

In my previous summary, I have mentioned a Codeforces problem. You are given two integers n and k. Your goal is to create a (multi-)set of positive integers such that among its sub(-multi-)sets we can find ones which sum to any integer between 1 and n, except k. n is at most 106, and the set you create must have at most 25 elements.

The first part of the solution is somewhat clear/standard: we need to be able to represent all numbers between 1 and k-1, but not the number k. For this, we can take all powers of two less than k: 1, 2, 4, ..., 2i, such that 2i<k<=2i+1, but then in order to not overshoot k we should replace 2i with k-2i: then the sum of all numbers is k-1, and clearly all numbers between 1 and k-1 are still representable.

Then, as long as all other numbers that we take into our set are at least k+1, k will still not be representable. But how do we cover all numbers between k+1 and n? After trying to come up with something concrete on paper unsuccessfully for some time, I've decided to just run a dynamic programming that remembers which numbers are representable, and repeatedly take the smallest non-representable number. It is not obvious why this will have at most 25 elements, but it is very easy to try.

Here is the output of this approach for n=1000000, k=5:
21
1 2 1 6 11 16 32 64 128 256 512 1024 2048 4096 8192 16384 32768 65536 131072 262144 524288

And for n=1000000, k=6:
21
1 2 2 7 13 19 38 76 152 304 608 1216 2432 4864 9728 19456 38912 77824 155648 311296 622592

Now we can notice a pattern: the numbers we add in the second phase are k+1, 2k+1, 3k+1, 2(3k+1), 4(3k+1), 8(3k+1), ...  We can now both be more confident that it will always fit under 25 elements, and also try to prove that this pattern always works. Or just submit. 

My submission in the actual contest is more complex than that, and even includes some randomization :) The reason for that is that I had a bug in the implementation of the simple dynamic programming which made me think it produces more than 25 elements sometimes, adding randomization helped fit under 25 but did not fix the bug, and after fixing the bug I did not check if randomization was still needed.

Thanks for reading, and check back next week!

Sunday, April 28, 2024

A jiangly week

The qualification round of the MIT Informatics Tournament "M(IT)^2" 2024 Spring Invitational took place last Sunday after I published my summary for the week (problems, results, top 5 on the left, analysis, my screencast). Mingyang Deng was very fast in general, but the final blow was that he could solve P4 a lot faster than other top competitors (25 minutes compared to about 40 for others), leading to a first place with quite significant margin. Congratulations!

More generally, it is awesome to see a new tournament with an onsite round (which was initially USA-only but has since expanded). It's a pity that I will not be able to make it since it was only announced a few weeks in advance. Good luck to all onsite participants, and have fun!

One of the rare TopCoder rounds, SRM 854, took place on Thursday (problems, results, top 5 on the left). This round has once again demonstrated TopCoder's unique format. Being the only competitor to solve the 1000-pointer was only enough for the 3rd place for Nyaan, since nwin and hitonanode accumulated an enormous amount of points during the challenge phase thanks to a corner case in the 350 that was not covered by the sample tests (and TopCoder does not check anything else on submission). Congratulations to all three!

One could argue that it is not very nice to (likely intentionally) exclude this corner case from the sample tests. However, I think it was fair game because there was this phrase in the problem statement: "Only the boxes matter, your final position does not: as soon as all the boxes are at the same coordinate, the task is complete regardless of your position at that moment." This phrase makes no sense if you miss this corner case, and since problemsetters do not write random things in statements, one had to stop and think why this was emphasized.

More generally, I think the TopCoder format with no pretests and therefore many submissions failing system tests often leads to very exciting rounds, allows more people to win from time to time, and also rewards those who can write code with less bugs and/or know which amount of testing is enough. I like all those properties, and therefore it is very sad that this format is slowly fading into the void.

Finally, Codeforces Round 941 wrapped up the week on Saturday (problems, results, top 5 on the left, analysis). The round had a bit fewer strong participants than usual since the Yandex Cup finalists could not participate, but still winning it was no small feat. jiangly was having a very good round, and was the first to solve problem D near the middle of the contest, but then he probably (or maybe not? Actually I have no idea) looked at the scoreboard and realized that people are solving E faster than D, and therefore even his great performance on the first four problems might not be enough to be in first place after five, simply because he chose the wrong order. On the other hand, rainboy had already demonstrated at that point that F is solvable in less than an hour, so going for F was a risky but potentially contest-winning move. And it worked out very nice for jiangly with 7 minutes remaining. Well done!

Some people even retire after achieving the first place in a Codeforces round, but even though for jiangly it is already the 13th first place, and he has also achieved the WTF first place last year and the ICPC first place last week, it feels that the story might be just starting :)

Problem B was a cute constructive one. You are given two integers n and k. Your goal is to create a (multi-)set of positive integers such that among its sub(-multi-)sets we can find ones which sum to any integer between 1 and n, except k. n is at most 106, and the set you create must have at most 25 elements. Can you find a way?

In my previous summary, I have mentioned a World Finals problem (problem T here): you are given two right square pyramids with integer base coordinates, integer height, and non-intersecting (but possibly touching) bases lying on the same plane. What is the shortest path between the tips of the pyramids that always goes either on the surface of one of the pyramids, or on the plane? The coordinates are up to 105, and the output must have relative error of at most 10-6.

This problem was not very beautiful. In fact, if you're after beautiful geometry problems, you should check out jeroenodb's recent post. But I think the problem was very educational, because it both demonstrated the need to understand one's own solution in detail, and the superiority of ternary search :)

First, we notice that on the suface of each pyramid we must go in a straight line, and the same is true for the plane. So our path will always have three straight segments, and the only choice we have is where the two points where those segments meet lie on the base of each pyramid.

This is where the two main approaches branch. Our approach, and the approach of many other teams who struggled with this problem during the round, went like this: for each base square, we consider 8 options (so 64 options total): the meeting point is either one of the four vertices, or on one of the four sides. If it is one of the vertices, we just need to find the shortest path from that vertex onwards. If it is on one of the sides, things are more complicated: we notice that we can "fold" the side of the pyramid over this side towards the plane, and the path lengths passing through this side will not change. Now we just need to find the shortest path on the plane between the folded locations of the pyramid tips, and the shortest path on the plane is a straight segment. However, for us to be able to "unfold" this segment back to a path in the original problem, we need to make sure that this segment actually intersects the side or two sides that we used for folding. So you spend quite some time implementing all this carefully, submit, and of course get WA.

The reason for the WA (and I was only able to figure this out because Kevin told me, I am not sure I would find this myself during the contest time at all) is that the segment shouldn't just intersect the two sides used for folding, it should intersect them in the correct order! The picture on the left demonstrates two tall pyramids standing next to each other, and how that leads to a segment that does intersect the two sides used for folding, but in the wrong order, and which therefore does not correspond to a valid path in the original problem. You fix this, and then you get OK.

However, there is a much better approach: it is somewhat clear (one can derive a formal proof from the first approach I guess, but one can also submit without proving) that if we fix which two sides of the base squares the two meeting points lie on, the path length is a convex function, so we can just use two nested ternary searches to get a very easy to implement solution with no corner cases. In this approach we don't even need to handle the base square vertices specially, they will be considered as part of the corresonding sides.

Thanks for reading, and check back next week!

Friday, April 19, 2024

A turning red week

The ICPC World Finals in Luxor was the main event of the week. The finals for two years were running in parallel, and our team was solving the mirror of the 2022 season, the 46th World Finals (problems, results, top 12 on the left, official broadcast, our stream). MIT team was dominating the round, solving 8 problems in less than two hours, getting to 9 within 3 hours, and leading by 2 problems for a long time. This reminds me of my own participation in 2005 World Finals (frozen detailed standings, final coarse standings), where we got to 7 problems slightly after the 3 hour mark and were leading 7-5, and with a huge advantage in penalty time, only to get stuck in problem G and have the Shanghai team overtake us thanks to solving 3 problems in the last hour. A very similar thing happened here with another team from China, Peking University, who solved 3 problems in the last 70 minutes and overtook the unlucky MIT team who was stuck with 25 incorrect attempts on problem S. Congratulations to both teams on the amazing performance!

Problem T "Carl's Vacation" was the trademark World Finals geometry problem. You are given two right square pyramids with integer base coordinates, integer height, and non-intersecting (but possibly touching) bases lying on the same plane. What is the shortest path between the tips of the pyramids that always goes either on the surface of one of the pyramids, or on the plane? The coordinates are up to 105, and the output must have relative error of at most 10-6. Can you see a way to implement this without getting stuck in the details?

The finals of the 2023 season, the 47th World Finals, had 5 shared problems and 6 unique problems (problems, results, top 5 on the left). The Higher School of Economics team took the lead a bit before the 2 hour mark and held it for most of the remaining contest, only allowing the MIPT team to lead briefly for 10 minutes in between. Unlike the first contest, nobody was able to solve the 10th problem and therefore HSE's win stood. Congratulations!

For me, the most amazing part of the Luxor event was meeting so many friends and acquaintances who live all over the world. The ICPC community is amazing by itself, and it also serves as a basis for multiple sub-communities that came together to meet in Luxor, such as the ICPC Live team, or the Universal Cup organizers and participants, or the ICPC alumni who now work for the sponsors, or just came as guests, such as myself. Huge thanks to the organizers, to the sponsors, and to the participants!

Thanks for reading, and check back next week.

Thursday, April 18, 2024

ICPC World Finals Luxor mirror stream

The ICPC World Finals in Luxor are happening tomorrow. You can find a lot of useful links here, but of course you should tune in to watch me, Gennady and Kevin solve the mirror round! We will start around noon Egypt time, maybe a bit earlier. To warm up, you can check out the previous streams we did with Mikhail instead of Kevin (2017, 2018, 2019, 2020).

Thursday, February 15, 2024

A NWERC week

The 2nd Universal Cup Stage 22: Hangzhou was the only event of last week (problems, results, top 5 on the left, analysis). Team USA1 has returned to the winning ways after a short slump in form, already leading on 12 problems but still finishing everything with an hour to spare. Congratulations!

Team "nwerc is bad" from the Univerity of Oxford also reminded that they are one of the favorites for one of the upcoming World Finals in Luxor by earning an excellent fourth place and being the best ICPC-active team this time. Well done!

Thanks for reading, and check back next week for more meaningful content :)

Wednesday, February 7, 2024

A Delft week

The 2nd Universal Cup. Stage 21: Delft was the main event of last week (problems, results, top 5 on the left, analysis). Team HoMaMaOvO won the round and continued closing the gap in the overall standings, which is now down to a mere 0.16 points. Winning one more stage (their 9th of the season) would be enough for them to overtake USA1, since it would bring at least 1/4*(3/4)**8*(200-175)~=0.62 points.

As both teams solved everything this time, the key advantage for HoMaMaOvO seems to have come from solving a tricky geometry problem B (and one can't complain that tricky geometry problems were unexpected) very fast from the first attempt. Well done!

Thanks for the (quick) reading, and check back next week.

Wednesday, January 31, 2024

A stable denominator week

TopCoder returned last week from another long break with SRM 852 (problems, results, top 5 on the left). The 1000-pointer was about counting k-th roots of a specific permutation, and it took the winner SSRS_ just 3.5 minutes since they reused their submission for a more general problem about counting k-th roots of any permutation. More generally this problem did not present as much of a challenge for the top participants as the 500-pointer, which saw many solutions fail and therefore offered a lot of challenge opportunities. Of the three contestants who managed to solve everything, kotatsugame was behind after the coding phase but managed to recover to the second place thanks to 200 challenge points. Congratulations to all three on the great performance!

The 2nd Universal Cup. Stage 20: Ōokayama followed, as usual, on Saturday (problems, results, top 5 on the left, analysis). 16 problems in a round is still a lot even by today's standards, but this still did not stop team HoMaMaOvO from solving all of them with 6 minutes to spare :) Well done! This is their 7th win of the season compared to 9 for USA1, and they are definitely still in contention for the overall first place.

Finally, Codeforces Round 921 wrapped up the competitive week also on Saturday (problems, results, top 5 on the left, analysis). Having dealt with the four easier problems in the first hour, I've decided to focus on problem F since there it seemed that we just need to solve the n=3 case and the rest will easily follow, while problem E gave some generating function vibes :) Unfortunately even the n=3 case in F turned out too hard for me. I have even implemented a brute force that tried different random strategies and it still could not solve n=3. The reason for that was probably the fact that the strategies I tried were non-adaptive: they asked the same questions irrespective of the answers.

Implementing a brute force over adaptive strategies seemed harder, so I've decided to give E another chance. I've realized it feels difficult because the number of choices we have always changes, therefore we are multplying probabilities with different denominators and it's not clear how to do the summation over different trajectories nicely. But then I remembered I already had this feeling in the past, and writing about this in my blog :) So I tried searching for [divide by the sum site:blog.mitrichev.ch], and for some reason this search returned what I was looking for on the first page. I've re-read that solution, and remembered the key trick: even if the overall number of choices is different every time, since all choices have the same probability at each step, the relative probability for a particular subset of choices of fixed size will always be the same. In the linked problem it was just 2 options each with probability 50%, while in problem E this time, if we focus on whether we visit a particular pair (x,y) or not, the number of choices affecting this outcome is always x+y, no matter the current state, so we can compute the probability of such visit happening very nicely.

There was still some work to do to improve the complexity from O(n2) to O(n*logn), and luckily I managed to do it before the end of the round. This was of course still not enough to compete with jiangly and others who focused on problem E earlier. Congratulations on the win!

Thanks for reading, and check back next week.

Sunday, January 21, 2024

A Frobenius week

The 2nd Universal Cup Stage 19: Estonia was the only event of this week (problems, results, top 5 on the left, analysis). Team 03 Slimes, who are a distant third in the overall standings, won their second stage of the season in an impressive fashion, beating the top two teams in the overall standings by two problems. Judging by the +32, some squeezing was involved, potentially of an approach that was not intended to pass — but that is also an important skill in algorthmic competitions, so well done! I am also not sure who actually was on the team this time, as Mingyang Deng is also listed as part of MIT CopyPaste on the same scoreboard.

Possibly rejuvenated by the news that the 2022 ICPC World Finals seem to be finally happening in April, Harbour.Space P+P+P followed in second place — congratulations as well! (jk, they actually wrote this contest as part of the Osijek camp back in September, so they were still practicing towards the original dates).

Finally, this week an anonymous LGM published a very nice result that drops a logarithmic factor from matrix exponentiation. Of course, theoretically we already know ways to drop much more from the asymptotic complexity, but all of those are not really beneficial in practice on the time limits prevalent in the algorithmic competitions. This result, however, did allow to slash the fastest time to find a power of a 200x200 matrix roughly 3x (compared to the straightforward binary exponentiation method; the judge also has some fast submissions from November 2023 that start with "vector<ll> f=a.poly();", so possibly some other version or precursor of this result?..

I guess now it will be a bit harder to separate wrong submissions when preparing problems, on the other hand the squeezing toolkit just got a bump :)

Thanks for reading, and check back next week!

Friday, January 19, 2024

A HoMaMaOvO week

The 2nd Universal Cup. Stage 18: Dolgoprudny was the only event of last week (problems, results, top 5 on the left, analysis). Team Hailiang FLS + RDFZ: Anonymous were the first to 6 problems at 1:58 into the contest, followed by Team HoMaMaOvO at 2:15. The remaining problems were much harder, and the teams were probably faced with tough choices between pursuing multiple problems in parallel and focusing on one problem to get it accepted. Both teams submitted 3 problems in the remaining time, and Anonymous were the first to get one of them accepted, but then HoMaMaOvO overtook them by getting two in the last hour. Congratulations to both teams!

In my previous summary, I have mentioned a Codeforces problem: consider a rooted tree where each node either has a leaf, or has exactly two children. For each node with two children, one of the edges to the children has weight 0, while the other has weight 1. We run a depth-first traversal of this tree starting from the root, and for each leaf we print the sum of weights on the path from the root to this leaf. Now we are given just the printed sequence and not the tree, and need to answer if there exists a tree as described above that could lead to this sequence being printed? For example, 2 1 0 1 1 can be printed, but 1 0 2 1 3 can't. The length of the given sequence is up to 2*105.

The key observation in this problem is to imagine that we are building our tree step by step. Initially, we have only the root, which is also a leaf, so the sequence printed will be just 0. Now we add two children to the root, using edges with weights 0 and 1, and the depth-first search traverses them in some order. Depending on this order, the sequence we get will become either 0 1 or 1 0. We continue by repeatedly taking a leaf and adding two children to it, and the sequence always changes in the following manner: if the number corresponding to this leaf was x, then we insert x+1 into this sequence either directly to the left or directly to the right of it. And we can apply this operation to any number in the sequence, so we just need to check if the sequence we are given can be obtained starting from just the number 0 using this operation repeatedly.

Now let us look at the process in reverse, in other words we will repeatedly remove numbers that are adjacent to a number smaller by 1 until only 0 is left. What should we remove first? All numbers in this sequence that are adjacent to a number smaller by 1 are candidates. Now consider what happens when we remove number x that was adjacent to x-1: now x-1 becomes adjacent to some other number y. When y=x-2, the number x-1 become a candidate. When y=x, the number y becomes a candidate. In all other cases, no new candidates appear. This means that the new appearing candidates are never bigger than the number being removed.

Therefore we can safely remove any of the candidates with the highest value: we will never get candidates with even higher value in the future because of the previous argument, therefore those numbers themselves will never be useful to support removal of another number. So we can just keep removing one of the remaining highest values while it is possible, and we either reach a single 0, in which case we have found a solution, or we get stuck when all remaining highest values cannot be removed, in which case there is no solution.

Thanks for reading, and check back for more!