My first non-competing round of the season… will see how many of these I bother to write up – if previous years is anything to go by, not many!
Q1) Given a set of tasks which each have a time to complete, and percentage chance of failure, determine the order which minimises the expected time to completion if failure of any task means you have to restart your order from scratch.
A1) This was a sneaky question, not insanely difficult, but they set the restrictions on the small input that the time to complete of every task was identical. In that scenario the solution is trivial, you sort by most likely to fail to least likely to fail (using a stable sort, or secondarily sorting based on original index).
The large input isn’t much more complicated, the only question is what is your sort operator look like when the times of the tasks are not identical. Unfortunately my initial guess would have been length of task times the chance of failure, when the correct answer is length of time divided by chance of success. Fraction class works here, or you can define your sort operator as a.Time*(100-b.FailChance) < b.Time*(100-a.FailChance). Pretty sure I’ve seen this question somewhere before…
Q2) Given a hexagonally shaped hexagonal grid and a series of ‘moves’ which fill in hexagons, determine the first point in time that either 2 corners are connected, 3 edges (which don’t include the corners) are connected, or a ring is formed. (A ring is a connected loop of hexagons where there is at least 1 empty hexagon inside.)
A2) The large input has a hex grid with side lengths of up to 3000, and 10k moves, so the implementation needs to be reasonably efficient. Having written LoopDeLoop, I’ve had opportunity to consider similar problems in the past, so I think I had a chance of solving this problem if I had of been in the round. We can solve the 3 sub-problems independently, but there will be a common theme.
Corners. if we use a union-find data structure and associate properties with each set representative (specifically the number of corners the set touches), we can do this in linear time. Each move is handled easily Each surrounding position is either empty or a member of a set. Gather the number of corners for the distinct sets, union them all together, add in the new position and set the number of corners equal to the total (adding 1 if the move is in a corner). If total exceeds 1 we are done.
Edges. Almost identical to the corners, only instead of summing, we use a 6bit number to represent the edges touched, and use bitwise or to combine them. And rather then checking total we do a pop-count.
Loops. Here is the tricky bit. Again we can use the union-find data structure, but this time rather than aiming to union things together and track status, we are looking to see if the current move has 2 neighbours which are already in the same set. There is also the possibility that the move doesn’t create a loop, but just fills in the edge of a ‘blob’. If precisely 2 of the neighbours are the same set, and they aren’t touching, we have definitely created a loop. If 3 of the neighbours are the same set and they aren’t ‘sequential’, we’ve created a loop. If 4, same applies. If 5 or more there isn’t anyway to not have them sequential (and specifically 6 can never happen since it is already a loop). Edge and corner placements don’t really change this logic, although you certainly can’t go all the way to 6.
Plenty of opportunity to screw up, but overall I think I had a better chance here than with the large input of the first question…
Q3) Given a delivery fee per delivery, and cost of types of food, and length of time until stale, how long can you eat delivered food given a certain amount of money.
A3) For a given number of days to survive on a single delivery, the answer is fairly obvious. For each day take the cheapest food which would not be stale.
The problem comes in deciding how many days to go between deliveries. Obviously if a type of food exceeds the cost of the cheapest food plus delivery fee, that type of food will never be selected, but since the delivery fee is averaged over multiple days the crossover point could be lower. It wouldn’t be too hard to do an infinite money scenario maximum efficiency but for a finite amount of money the problem is trickier.
The large input is huge, 10^18 money and up to 10^18 stale time. But the number of food types is much smaller, only 200. I looked up some solutions, and the one which I think most matches where my thinking was heading is assuming that the solution consists of d day cycles or d-1 cycles to make up the total number of days achievable. Binary search to find the maximum number of days, and ternary search inside that to find the minimum cost to complete that many days. If minimum cost is below the total amount of money, binary search goes up, otherwise it goes down.
Q4) Given a sequence of characters, some of which can be substituted with numbers, determine the length of the shortest string which contains every possible length k consecutive subsequence of the original sequence or any of its substituted variations.
A4) Large input has k up to 500, small input is only 2. Small input we can build the list of distinct pairs, including substitutions. Then you have to work out how short you can make the joining. For each distinct pair you have to have at least 1 character in the output string, but do you need 2? For each unique character in the input we can count how many pairs start with it, and how many end with it. The difference (if positive) is how many times you can’t reuse that character. Finally even if you could theoretically reuse everything, there is always the first character, so there has to be something which can’t be reused. (I read a solution because I wanted to finish writing this blog post before 1am… but I don’t think this is too hard to come up with…)
The large input, I have no clue! Unlike with the size 2 input, you can have multiple different length overlaps to save you characters. (Not to mention that 500 character sequence can have 2^500 versions using substitutions…) Only 1 person solved this during the competition, so I don’t feel to bad… Turns out he used a mincost/maxflow, although I certainly am not going to read the pages of code to try and understand how mincost/maxflow solves this problem…