22Problems inspired by the [International Collegiate Programming Contest](https://icpc.global) (ICPC).
33"""
44
5- from problems import Problem
5+ from puzzle_generator import PuzzleGenerator
66from typing import List
77
88
9- # Hint: subclass Problem.Debug for quick testing. Run make_dataset.py to make the dataset
10- # See https://github.com/microsoft/PythonProgrammingPuzzles/wiki/How-to-add-a-puzzle for more info
9+ # See https://github.com/microsoft/PythonProgrammingPuzzles/wiki/How-to-add-a-puzzle to learn about adding puzzles
1110
1211
13- class BiPermutations (Problem ):
14- """There are two rows of objects. Given the length-n integer arrays of prices and heights of objects in each
15- row, find a permutation of both rows so that the permuted prices are non-decreasing in each row and
16- so that the first row is taller than the second row.
17-
12+ class BiPermutations (PuzzleGenerator ):
13+ """
1814 Inspired by
1915 [ICPC 2019 Problem A: Azulejos](https://icpc.global/newcms/worldfinals/problems/2019%20ACM-ICPC%20World%20Finals/icpc2019.pdf)
20- which is 2,287 characters."""
16+ which is 2,287 characters.
17+ """
2118
2219 @staticmethod
2320 def sat (perms : List [List [int ]],
2421 prices0 = [7 , 7 , 9 , 5 , 3 , 7 , 1 , 2 ],
2522 prices1 = [5 , 5 , 5 , 4 , 2 , 5 , 1 , 1 ],
2623 heights0 = [2 , 4 , 9 , 3 , 8 , 5 , 5 , 4 ],
2724 heights1 = [1 , 3 , 8 , 1 , 5 , 4 , 4 , 2 ]):
25+ """
26+ There are two rows of objects. Given the length-n integer arrays of prices and heights of objects in each
27+ row, find a permutation of both rows so that the permuted prices are non-decreasing in each row and
28+ so that the first row is taller than the second row.
29+ """
2830 n = len (prices0 )
2931 perm0 , perm1 = perms
3032 assert sorted (perm0 ) == sorted (perm1 ) == list (range (n )), "Solution must be two permutations"
@@ -74,20 +76,8 @@ def gen_random(self):
7476 self .add (dict (prices0 = prices0 , heights0 = heights0 , prices1 = prices1 , heights1 = heights1 ))
7577
7678
77- class OptimalBridges (Problem ):
79+ class OptimalBridges (PuzzleGenerator ):
7880 """
79- You are to choose locations for bridge bases from among a given set of mountain peaks located at
80- `xs, ys`, where `xs` and `ys` are lists of n integers of the same length. Your answer should be a sorted
81- list of indices starting at 0 and ending at n-1. The goal is to minimize building costs such that the bridges
82- are feasible. The bridges are all semicircles placed on top of the pillars. The feasibility constraints are that:
83- * The bridges may not extend above a given height `H`. Mathematically, if the distance between the two xs
84- of adjacent pillars is d, then the semicircle will have radius `d/2` and therefore the heights of the
85- selected mountain peaks must both be at most `H - d/2`.
86- * The bridges must clear all the mountain peaks, which means that the semicircle must lie above the tops of the
87- peak. See the code for how this is determined mathematically.
88- * The total cost of all the bridges must be at most `thresh`, where the cost is parameter alpha * (the sum of
89- all pillar heights) + beta * (the sum of the squared diameters)
90-
9181 Inspired by
9282 [ICPC 2019 Problem B: Bridges](https://icpc.global/newcms/worldfinals/problems/2019%20ACM-ICPC%20World%20Finals/icpc2019.pdf)
9383 which is 3,003 characters.
@@ -101,6 +91,19 @@ def sat(indices: List[int],
10191 xs = [0 , 10 , 20 , 30 , 50 , 80 , 100 , 120 , 160 , 190 , 200 ],
10292 ys = [0 , 30 , 10 , 30 , 50 , 40 , 10 , 20 , 20 , 55 , 10 ],
10393 thresh = 26020 ):
94+ """
95+ You are to choose locations for bridge bases from among a given set of mountain peaks located at
96+ `xs, ys`, where `xs` and `ys` are lists of n integers of the same length. Your answer should be a sorted
97+ list of indices starting at 0 and ending at n-1. The goal is to minimize building costs such that the bridges
98+ are feasible. The bridges are all semicircles placed on top of the pillars. The feasibility constraints are that:
99+ * The bridges may not extend above a given height `H`. Mathematically, if the distance between the two xs
100+ of adjacent pillars is d, then the semicircle will have radius `d/2` and therefore the heights of the
101+ selected mountain peaks must both be at most `H - d/2`.
102+ * The bridges must clear all the mountain peaks, which means that the semicircle must lie above the tops of the
103+ peak. See the code for how this is determined mathematically.
104+ * The total cost of all the bridges must be at most `thresh`, where the cost is parameter alpha * (the sum of
105+ all pillar heights) + beta * (the sum of the squared diameters)
106+ """
104107 assert sorted ({0 , len (xs ) - 1 , * indices }) == indices , f"Ans. should be sorted list [0, ..., { len (xs ) - 1 } ]"
105108 cost = alpha * (H - ys [0 ])
106109 for i , j in zip (indices , indices [1 :]):
@@ -183,26 +186,30 @@ def gen_random(self):
183186 self .add (dict (H = H , alpha = alpha , beta = beta , xs = xs , ys = ys , thresh = thresh ))
184187
185188
186- class CheckersPosition (Problem ):
187- """You are given a partial transcript a checkers game. Find an initial position such that the transcript
188- would be a legal set of moves. The board positions are [x, y] pairs with 0 <= x, y < 8 and x + y even.
189- There are two players which we call -1 and 1 for convenience, and player 1 must move first in transcript.
190- The initial position is represented as a list [x, y, piece] where piece means:
191- * 0 is empty square
192- * 1 or -1 is piece that moves only in the y = 1 or y = -1 dir, respectively
193- * 2 or -2 is king for player 1 or player 2 respectively
194-
195- Additional rules:
196- * You must jump if you can, and you must continue jumping until one can't any longer.
197- * You cannot start the position with any non-kings on your last rank.
198- * Promotion happens after the turn ends
199-
189+ class CheckersPosition (PuzzleGenerator ):
190+ """
200191 Inspired by
201192 [ICPC 2019 Problem C: Checks Post Facto](https://icpc.global/newcms/worldfinals/problems/2019%20ACM-ICPC%20World%20Finals/icpc2019.pdf)
193+
194+ Nobody solved this problem during the competition -- it is pretty difficult!
202195 """
203196
204197 @staticmethod
205198 def sat (position : List [List [int ]], transcript = [[[3 , 3 ], [5 , 5 ], [3 , 7 ]], [[5 , 3 ], [6 , 4 ]]]):
199+ """
200+ You are given a partial transcript a checkers game. Find an initial position such that the transcript
201+ would be a legal set of moves. The board positions are [x, y] pairs with 0 <= x, y < 8 and x + y even.
202+ There are two players which we call -1 and 1 for convenience, and player 1 must move first in transcript.
203+ The initial position is represented as a list [x, y, piece] where piece means:
204+ * 0 is empty square
205+ * 1 or -1 is piece that moves only in the y = 1 or y = -1 dir, respectively
206+ * 2 or -2 is king for player 1 or player 2 respectively
207+
208+ Additional rules:
209+ * You must jump if you can, and you must continue jumping until one can't any longer.
210+ * You cannot start the position with any non-kings on your last rank.
211+ * Promotion happens after the turn ends
212+ """
206213 board = {(x , y ): 0 for x in range (8 ) for y in range (8 ) if (x + y ) % 2 == 0 } # empty board, 0 = empty
207214 for x , y , p in position :
208215 assert - 2 <= p <= 2 and board [x , y ] == 0 # -1, 1 is regular piece, -2, 2 is king
@@ -427,22 +434,23 @@ def make_random_move(sign): # returns True if move was made else False if no le
427434 return [[list (a ) for a in move ] for move in transcript ] # convert to lists
428435
429436
430- class MatchingMarkers (Problem ):
437+ class MatchingMarkers (PuzzleGenerator ):
431438 """
432- The input is a string of start and end markers "aaBAcGeg" where upper-case characters indicate start markers
433- and lower-case characters indicate ending markers. The string indicates a ring (joined at the ends) and the goal is
434- to find a location to split the ring so that there are a maximal number of matched start/end chars where a character
435- (like "a"/"A") is matched if starting at the split and going around the ring, the start-end pairs form a valid
436- nesting like nested parentheses.
437-
438- This is trivial in quadratic time, but the challenge is to solve it quickly (i.e., linear time).
439-
440439 Inspired by
441440 [ICPC 2019 Problem D: Circular DNA](https://icpc.global/newcms/worldfinals/problems/2019%20ACM-ICPC%20World%20Finals/icpc2019.pdf)
441+
442+ This is trivial in quadratic time, but the challenge is to solve it quickly (i.e., linear time).
442443 """
443444
444445 @staticmethod
445446 def sat (cut_position : int , ring = "yRrsmOkLCHSDJywpVDEDsjgCwSUmtvHMefxxPFdmBIpM" , lower = 5 ):
447+ """
448+ The input is a string of start and end markers "aaBAcGeg" where upper-case characters indicate start markers
449+ and lower-case characters indicate ending markers. The string indicates a ring (joined at the ends) and the goal is
450+ to find a location to split the ring so that there are a maximal number of matched start/end chars where a character
451+ (like "a"/"A") is matched if starting at the split and going around the ring, the start-end pairs form a valid
452+ nesting like nested parentheses. Can you solve it in linear time?
453+ """
446454 line = ring [cut_position :] + ring [:cut_position ]
447455 matches = {c : 0 for c in line .lower ()}
448456 for c in line :
@@ -494,4 +502,4 @@ def gen_random(self):
494502
495503
496504if __name__ == "__main__" :
497- Problem .debug_problems ()
505+ PuzzleGenerator .debug_problems ()
0 commit comments