├── 2017 ├── Junior │ └── quadrantSection.py └── Senior │ └── nailedIt.py ├── 2018 ├── Junior │ ├── chooseYourOwnPath.py │ └── chooseYourOwnPathImproved.py ├── README.md └── Senior │ ├── balancedTrees.py │ ├── roboThieves.py │ ├── sunflowers.py │ └── voronoiVillages.py ├── 2019 ├── Junior │ └── ruleOfThree.py ├── README.md └── Senior │ ├── ArithmeticSquare.py │ ├── prettyAveragePrimes.cpp │ ├── prettyAveragePrimes_Alternative.py │ ├── tourism.cpp │ └── triangle.cpp ├── 2021 └── Senior │ ├── s1.py │ ├── s2.py │ ├── s3.py │ ├── s4.py │ └── s5.py └── README.md /2017/Junior/quadrantSection.py: -------------------------------------------------------------------------------- 1 | # Junior 1 2 | 3 | """A common problem in mathematics is to determine which quadrant a given point lies in. There are four quadrants, numbered from 1 to 4, as shown in the diagram below: 4 | 5 | 6 | For example, the point A, which is at coordinates (12,5) lies in quadrant 1 since both its x and y values are positive, and point B lies in quadrant 2 since its x value is negative and its y value is positive. 7 | 8 | Your job is to take a point and determine the quadrant it is in. You can assume that neither of the two coordinates will be 0. 9 | 10 | [Input Specification] 11 | The first line of input contains the integer x (−1000≤x≤1000;x≠0). The second line of input contains the integer y (−1000≤y≤1000;y≠0). 12 | 13 | [Output Specification] 14 | Output the quadrant number (1, 2, 3 or 4) for the point (x,y).""" 15 | 16 | 17 | from sys import stdin 18 | 19 | x = int(stdin.readline()) 20 | y = int(stdin.readline()) 21 | 22 | if x > 0: 23 | print([1, 4][y < 0]) 24 | else: 25 | print([2, 3][y < 0]) 26 | 27 | -------------------------------------------------------------------------------- /2017/Senior/nailedIt.py: -------------------------------------------------------------------------------- 1 | 2 | # CCC Senior 3 / Junior 5 3 | 4 | """Tudor is a contestant in the Canadian Carpentry Challenge (CCC). To win the CCC, Tudor must demonstrate his skill at nailing wood together to make the longest fence possible using boards. To accomplish this goal, he has N pieces of wood. The ith piece of wood has integer length Li. 5 | 6 | A board is made up of exactly two pieces of wood. The length of a board made of wood with lengths Li and Lj is Li+Lj. A fence consists of boards that are the same length. The length of the fence is the number of boards used to make it, and the height of the fence is the length of each board in the fence. In the example fence below, the length of the fence is 4; the height of the fence is 50; and, the length of each piece of wood is shown: 7 | 8 | 9 | Tudor would like to make the longest fence possible. Please help him determine the maximum length of any fence he could make, and the number of different heights a fence of that maximum length could have. 10 | 11 | [Input Specification] 12 | The first line will contain the integer N (2 ≤ N ≤ 1000000). 13 | The second line will contain N space-separated integers L1,L2,…,LN (1 ≤ Li ≤ 2000). 14 | 15 | For 5 of the 15 available marks, N≤100. 16 | For an additional 4 of the 15 available marks, N ≤ 1000. 17 | For an additional 3 of the 15 available marks, N ≤ 100000. 18 | 19 | [Output Specification] 20 | Output two integers on a single line separated by a single space: the length of the longest fence and the number of different heights a longest fence could have.""" 21 | 22 | 23 | from sys import stdin 24 | 25 | def nail(boards): 26 | heights = {} 27 | get = heights.get 28 | for boardA in range(1, 2001): 29 | if boardA not in boards: 30 | continue 31 | heights[boardA * 2] = get(boardA * 2, 0) + (boards[boardA] >> 1) 32 | for boardB in range(boardA + 1, 2001): 33 | if boardB not in boards: 34 | continue 35 | height = boardA + boardB 36 | heights[height] = get(height, 0) + min(boards[boardA], boards[boardB]) 37 | maximum = -1 38 | counter = 0 39 | for i in heights: 40 | i = heights[i] 41 | if i > maximum: 42 | maximum = i 43 | counter = 0 44 | if i == maximum: 45 | counter += 1 46 | print(maximum, counter) 47 | return None 48 | 49 | stdin.readline() 50 | freq = {} 51 | for i in map(int, stdin.readline().split()): 52 | freq[i] = freq.get(i, 0) + 1 53 | nail(freq) 54 | -------------------------------------------------------------------------------- /2018/Junior/chooseYourOwnPath.py: -------------------------------------------------------------------------------- 1 | """Canadian Computing Competition: 2018 Stage 1, Junior #5 2 | There is a genre of fiction called choose your own adventure books. These books allow the reader to make choices for the characters which alters the outcome of the story. 3 | 4 | For example, after reading the first page of a book, the reader may be asked a choice, such as "Do you pick up the rock?" If the reader answers "yes", they are directed to continue reading on page 47, and if they choose "no", they are directed to continue reading on page 18. On each of those pages, they have further choices, and so on, throughout the book. Some pages do not have any choices, and thus these are the "ending" pages of that version of the story. There may be many such ending pages in the book, some of which are good (e.g., the hero finds treasure) and others which are not (e.g., the hero finds a leftover sandwich from 2001). 5 | 6 | You are the editor of one of these books, and you must examine two features of the choose your own adventure book: 7 | 8 | ensure that every page can be reached – otherwise, there is no reason to pay to print a page which no one can ever read; 9 | find the shortest path, so that readers will know what the shortest amount of time they need to finish one version of the story. 10 | Given a description of the book, examine these two features. 11 | 12 | Input Specification 13 | The first line of input contains N (1≤N≤10000), the number of pages in the book. Each of the next N lines contain an integer Mi (1≤i≤N;0≤Mi≤N), which is the number of different options from page i, followed by Mi space-separated integers in the range from 1 to N, corresponding to each of the pages to go to next from page i. It will also be the case M1+M2+⋯+MN is at most 10000. 14 | 15 | If Mi=0, then page i is an ending page (i.e., there are no choices from that page). There will be at least one ending page in the book. 16 | 17 | Note that you always begin the book on page 1. 18 | 19 | For 4 of the available 15 marks, N≤100, Mi≤10 for 1≤i≤N. 20 | 21 | For an additional 3 of the available 15 marks, the book is guaranteed to have no cycles. 22 | 23 | For an additional 4 of the available 15 marks, N≤1000, Mi≤25 for 1≤i≤N. 24 | 25 | Output Specification 26 | The output will be two lines. The first line will contain Y if all pages are reachable, and N otherwise. 27 | 28 | The last line will contain a non-negative integer K, which is the shortest path a reader can take while reading this book. There will always be a finite shortest path.""" 29 | 30 | 31 | ### Approach: Run two BFS, first to check if all nodes can be reached, second to find the shortest path. 32 | 33 | 34 | vertices = int(input()) 35 | adjList = [] 36 | for i in range(vertices): 37 | adjList.append([int(node) - 1 for node in input().split()][1:] or []) 38 | queue = {0} 39 | visited = set() 40 | ### First BFS 41 | while queue: 42 | visited |= queue 43 | queue = {node for vertice in queue for node in adjList[vertice] if node not in visited} 44 | print("NY"[len(visited) == vertices]) 45 | 46 | ### Second BFS 47 | queue = [[0]] 48 | visited = set() 49 | while queue: 50 | path = queue.pop(0) 51 | node = path[-1] 52 | if len(adjList[node]) == 0: 53 | break 54 | for vertice in adjList[node]: 55 | if vertice in visited: 56 | pass 57 | visited.add(vertice) 58 | queue.append(path + [vertice]) 59 | print(len(path)) 60 | -------------------------------------------------------------------------------- /2018/Junior/chooseYourOwnPathImproved.py: -------------------------------------------------------------------------------- 1 | ###See other file for the problem description 2 | 3 | 4 | 5 | #On DMOJ, this passed the tests in py3 in 1.35 seconds and took 10.3MB of memory which was a pretty decent amount lower than the other top python3 solutions. 6 | 7 | #Currently used speedup techniques: deque, bitmask marking 8 | #Additional speedup tricks not implemented: 9 | # 1- bi-directional search 10 | # 2- better input parsing 11 | # 3- convert graph into bitmasks, then iterate through with bit operations 12 | 13 | import sys 14 | from collections import deque 15 | 16 | vertices = int(sys.stdin.readline()) #input 17 | adjList = [[int(node) - 1 for node in sys.stdin.readline().split()[1:]] for i in range(vertices)] #Build graph 18 | queue = deque([[0]]) #Initialize queue 19 | visited = 1 #Use bitmask to track visited vertices, this will help save some memory and speed (Faster than set lookup) 20 | lowest = -1 #Shortest path 21 | while queue: 22 | path = queue.popleft() 23 | node = path[-1] 24 | if not adjList[node] and lowest < 0: 25 | lowest = len(path) 26 | if lowest > -1 and visited + 1 == 1 << vertices: 27 | break 28 | for vertice in adjList[node]: 29 | if visited >> vertice & 1: #Grab kth bit of bitmask, check if it visited or not 30 | continue 31 | visited |= 1 << vertice #Mark it 32 | if lowest < 0: 33 | queue.append(path + [vertice]) #Memory save, no need to keep path if you already found the shortest path 34 | else: 35 | queue.append([vertice]) 36 | print("NY"[visited + 1 == 1 << vertices]) # 1 + visited == 2 ** vertices, which means all the bits in the bitmask got marked 37 | print(lowest) 38 | -------------------------------------------------------------------------------- /2018/README.md: -------------------------------------------------------------------------------- 1 | ## CCC 2018 Solutions 2 | 3 | The problem statements can be found on [DMOJ](https://dmoj.ca/) and on the [CCC Grader](https://cccgrader.com/). 4 | 5 | For 2018 here is the list of problems for both divisions: 6 | 7 | ##### Junior Division 8 | - **J1** - Telemarketer or not? 9 | - **J2** - Occupy parking 10 | - **J3** - Are we there yet? 11 | - **J4/S2** - Sunflowers 12 | - **J5** - Choose your own path 13 | 14 | ##### Senior Division 15 | - **S1** - Voronoi Villages 16 | - **S2/J4** - Sunflowers 17 | - **S3** - RoboThieves 18 | - **S4** - Balanced Trees 19 | - **S5** - Maximum Strategic Savings 20 | 21 | ### Problem Source 22 | https://cemc.math.uwaterloo.ca/contests/computing.html 23 | -------------------------------------------------------------------------------- /2018/Senior/balancedTrees.py: -------------------------------------------------------------------------------- 1 | """Trees have many fascinating properties. While this is primarily true for trees in nature, the concept of trees in math and computer science is also interesting. A particular kind of tree, a perfectly balanced tree, is defined as follows. 2 | 3 | Every perfectly balanced tree has a positive integer weight. A perfectly balanced tree of weight 1 always consists of a single node. Otherwise, if the weight of a perfectly balanced tree is w and w≥2, then the tree consists of a root node with branches to k subtrees, such that 2≤k≤w. In this case, all k subtrees must be completely identical, and be perfectly balanced themselves. 4 | 5 | In particular, all k subtrees must have the same weight. This common weight must be the maximum integer value such that the sum of the weights of all k subtrees does not exceed w, the weight of the overall tree. For example, if a perfectly balanced tree of weight 8 has 3 subtrees, then each subtree would have weight 2, since 2+2+2=6≤8. 6 | 7 | Given N, find the number of perfectly balanced trees with weight N. 8 | 9 | [Input Specification] 10 | The input will be a single line containing the integer N (1≤N≤10^9). 11 | 12 | For 5 of the 15 marks available, N≤1000. 13 | 14 | For an additional 2 of the 15 marks available, N≤50000. 15 | 16 | For an additional 2 of the 15 marks available, N≤106. 17 | 18 | [Output Specification] 19 | Output a single integer, the number of perfectly balanced trees with weight N.""" 20 | 21 | # Technically only passed with PyPy on dmoj, hopefully this passes in the real CCC but I'm too scared to try it :P 22 | 23 | # Recurrence formula: a(n) = a([ n/2 ]) + a([ n/3 ]) + . . . + a([ n/n ]) 24 | # See https://oeis.org/A022825 for more info (there isn't a lot) 25 | 26 | 27 | # Method: Recursion + memoization 28 | # Instead of doing n/2, n/3 ... n/n, I used a formula to count the frequency of each number 29 | from sys import stdin 30 | N = int(stdin.readline()) 31 | memo = {} 32 | def f(n): 33 | if n == 1: #Base case, one "tree" always has one node 34 | return 1 35 | if n not in memo: 36 | total = n - (n >> 1) 37 | kth = 2 38 | while True: 39 | partition = n // kth 40 | if partition ** 2 > n: 41 | total += f(kth) * (partition - n // (kth + 1)) + f(partition) 42 | elif kth == partition: 43 | total += f(kth) * (partition - n // (kth + 1)) 44 | else: 45 | break 46 | kth += 1 47 | memo[n] = total 48 | return memo[n] 49 | print(f(N)) 50 | -------------------------------------------------------------------------------- /2018/Senior/roboThieves.py: -------------------------------------------------------------------------------- 1 | # CCC 2018 Senior 3 2 | # Straightforward algorithm: Run a BFS, keep track of camera tiles and special tiles 3 | 4 | length, width = map(int, input().split()) 5 | 6 | grid = [input() for i in range(length)] 7 | dead = set() 8 | for i in range(length): 9 | print(grid[i]) 10 | for j in range(width): 11 | if grid[i][j] == "S": 12 | queue = [[i, j, 0]] 13 | if grid[i][j] != "C": 14 | continue 15 | dead.add((i, j)) 16 | for a, b in [0, 1], [0, -1], [-1, 0], [1, 0]: 17 | I = i 18 | J = j 19 | while grid[I + a][J + b] != "W": 20 | I += a 21 | J += b 22 | if grid[I][J] in "S.": 23 | dead.add((I, J)) 24 | 25 | vis = {} 26 | loop = set() 27 | for x, y, moves in queue: 28 | while grid[x][y] in "UDLR": 29 | if grid[x][y] == "U": 30 | if grid[x - 1][y] == "W": 31 | break 32 | x -= 1 33 | elif grid[x][y] == "D": 34 | if grid[x + 1][y] == "W": 35 | break 36 | x += 1 37 | elif grid[x][y] == "L": 38 | if grid[x][y - 1] == "W": 39 | break 40 | y -= 1 41 | elif grid[x][y] == "R": 42 | if grid[x][y + 1] == "W": 43 | break 44 | y += 1 45 | if (x, y) in loop: 46 | break 47 | loop.add((x, y)) 48 | if (x, y) not in dead and (x, y) not in vis and grid[x][y] in "S.": 49 | vis[x, y] = moves 50 | for a, b in [0, 1], [0, -1], [-1, 0], [1, 0]: 51 | a += x 52 | b += y 53 | queue.append([a, b, moves + 1]) 54 | 55 | for i in range(length): 56 | for j in range(width): 57 | if grid[i][j] == ".": 58 | print(vis.get((i, j), -1)) 59 | -------------------------------------------------------------------------------- /2018/Senior/sunflowers.py: -------------------------------------------------------------------------------- 1 | """Barbara plants N different sunflowers, each with a unique height, ordered from smallest to largest, and records their heights for N consecutive days. Each day, all of her flowers grow taller than they were the day before. 2 | 3 | She records each of these measurements in a table, with one row for each plant, with the first row recording the shortest sunflower's growth and the last row recording the tallest sunflower's growth. 4 | 5 | The leftmost column is the first measurement for each sunflower, and the rightmost column is the last measurement for each sunflower. 6 | 7 | If a sunflower was smaller than another when initially planted, it remains smaller for every measurement. 8 | 9 | Unfortunately, her children may have altered her measurements by rotating her table by a multiple of 90 degrees. 10 | 11 | Your job is to help Barbara determine her original data. 12 | 13 | [Input Specification] 14 | The first line of input contains the number N (2≤N≤100). The next N lines each contain N positive integers, each of which is at most 109 . It is guaranteed that the input grid represents a rotated version of Barbara's grid. 15 | 16 | [Output Specification] 17 | Output Barbara's original data, consisting of N lines, each of which contain N positive integers.""" 18 | 19 | 20 | grid = [[*map(int, input().split())] for i in range(int(input()))] 21 | 22 | lowest = min(map(min, grid)) 23 | 24 | while lowest != grid[0][0]: 25 | grid = [*zip(*grid[::-1])] 26 | 27 | for i in grid: 28 | print(" ".join(map(str, i))) 29 | -------------------------------------------------------------------------------- /2018/Senior/voronoiVillages.py: -------------------------------------------------------------------------------- 1 | """Canadian Computing Competition: 2018 Stage 1, Senior #1 2 | In the country of Voronoi, there are N villages, located at distinct points on a straight road. Each of these villages will be represented by an integer position along this road. 3 | 4 | Each village defines its neighbourhood as all points along the road which are closer to it than to any other village. A point which is equally close to two distinct villages A and B is in the neighbourhood of A and also in the neighbourhood of B. 5 | 6 | Each neighbourhood has a size which is the difference between the minimum (leftmost) point in its neighbourhood and the maximum (rightmost) point in its neighbourhood. 7 | 8 | The neighbourhoods of the leftmost and rightmost villages are defined to be of infinite size, while all other neighbourhoods are finite in size. 9 | 10 | Determine the smallest size of any of the neighbourhoods (with exactly 1 digit after the decimal point). 11 | 12 | [Input Specification] 13 | The first line will contain the number N (3 ≤ N ≤ 100), the number of villages. On the next N lines there will be one integer per line, where the ith line contains the integer Vi , the position of the ith village (−1000000000≤Vi≤1000000000). All villages are at distinct positions. 14 | 15 | [Output Specification] 16 | Output the smallest neighbourhood size with exactly one digit after the decimal point.""" 17 | 18 | 19 | # Sort, get midpoint of each adjacent village, find minimum. 20 | 21 | villages = sorted(int(input()) for index in range(int(input()))) 22 | smallest = float("Inf") 23 | for index in range(2, len(villages)): 24 | smallest = min(smallest, (villages[index] - villages[index - 2]) / 2) 25 | print("%.1f" % smallest) 26 | -------------------------------------------------------------------------------- /2019/Junior/ruleOfThree.py: -------------------------------------------------------------------------------- 1 | # Main idea is to use recursion to go through each substitution 2 | # To speed this up, we will NOT go through identical states (# of steps, state) 3 | 4 | # I wrote senior this year so I cannot confirm this will pass, but running my own tests shows that it's capable of doing steps=15 :) 5 | # EDIT: confirmed it passed! 6 | 7 | # Further optimizations if this does not pass: 8 | # - Meet-in-the-middle trick (precompute half of the paths) 9 | # Should be able to reduce complexity by about a square root 10 | # 11 | # - Better pattern matching for each rule 12 | # Right now it is O(n^2) for each rule, we can improve this by either using Rabin Karp or any linear string function. 13 | # 14 | # - Convert to binary 15 | # Bitwise operations will speed up: 16 | # - Memoizing each state [O(1) instead of O(n)] 17 | # - Pattern matching [O(n) instead of O(n^2)] 18 | # - Creating the new string after substitution [O(1) instead of O(n)] 19 | 20 | # So yeah lots can be done to improve this current algorithm :) 21 | 22 | from sys import stdin 23 | input = stdin.readline 24 | 25 | t1, d1 = input().split() 26 | t2, d2 = input().split() 27 | t3, d3 = input().split() 28 | 29 | steps, src, tgt = input().split() 30 | steps = int(steps) 31 | 32 | # Main function 33 | 34 | memo = set() 35 | 36 | def helper(cur, state, moves): 37 | 38 | # We have reached the amount of steps and we have reached the target 39 | 40 | if cur == 0 and state == tgt: 41 | return moves 42 | 43 | # We ran out of steps and it's still different 44 | 45 | if cur == 0: 46 | return False 47 | 48 | # The most important part. This will allow the solution to pass for steps=15 49 | 50 | # Main idea here is that we avoid computing the cases where different combinations will lead to the same state 51 | 52 | label = cur, state 53 | 54 | if label in memo: 55 | return False # Avoid it 56 | 57 | memo.add(label) # Don't go through this stage again 58 | 59 | # Go through each rule 60 | 61 | for rule, (a, b) in enumerate([(t1, d1), (t2, d2), (t3, d3)], 1): 62 | 63 | # Find all potential positions to use the rule 64 | 65 | l = len(a) 66 | pos = -1 67 | 68 | while 1: 69 | 70 | pos = state.find(a, pos + 1) 71 | 72 | if pos == -1: # If it doesn't find anything 73 | break 74 | 75 | # Create the new replaced sequence 76 | 77 | new_state = state[:pos] + b + state[pos + l:] 78 | output = helper(cur - 1, new_state[:], moves + [(rule, pos, new_state)]) 79 | 80 | if output: # We found it! 81 | return output 82 | 83 | return False 84 | 85 | value = helper(steps, src, []) 86 | 87 | if not value: 88 | print("IMPOSSIBLE") # just in case, in the real problem this will never happen 89 | else: 90 | for i, j, k in value: 91 | print(i, j + 1, k) 92 | -------------------------------------------------------------------------------- /2019/README.md: -------------------------------------------------------------------------------- 1 | ## CCC 2019 Solutions 2 | 3 | The problem statements can be found on [DMOJ](https://dmoj.ca/) and on the [CCC Grader](https://cccgrader.com/). 4 | 5 | For 2019 here is the list of problems for both divisions: 6 | 7 | ##### Junior Division 8 | - **J1** - Winning Score 9 | - **J2** - Time to Decompress 10 | - **J3** - Cold Compress 11 | - **J4/S1** - Flipper 12 | - **J5** - Rule Of Three 13 | 14 | ##### Senior Division 15 | - **S1/J4** - Flipper 16 | - **S2** - Pretty Average Primes 17 | - **S3** - Arithmetic Square 18 | - **S4** - Tourism 19 | - **S5** - Triangle: The Data Structure 20 | 21 | ### Problem Source 22 | https://cemc.math.uwaterloo.ca/contests/computing.html 23 | -------------------------------------------------------------------------------- /2019/Senior/ArithmeticSquare.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | 3 | # Outline of approach because this problem tripped lots of people (including me) during the contest: 4 | # 5 | # 1 - While the grid is not completely filled in: 6 | # 1a - Fill in anything that can be deduced (ex: there is only 1 X in a row, we can deduce that value) 7 | # 1b - Find all leftover X spots 8 | # 1c - Generate a random value to put in that square 9 | # The reason this works is because the only real cases where there are leftover spots is when there are very few numbers actually filled in. 10 | # Therefore there can be many different solutions to that grid. 11 | # In fact, there are so many that we can just put a random value in there and try another one if it fails. 12 | # 13 | # This method is pretty short, takes less than 40 lines to write (compared to my original 200 lines solution) 14 | 15 | 16 | # Function that will fill in any number that can be deduced 17 | def fill(grid): 18 | for _ in xrange(9): 19 | for side in xrange(2): 20 | for pos, (i, j, k) in enumerate(grid): 21 | if j == "X" and i != "X" != k: 22 | j = i + k >> 1 23 | elif k == "X" and i != "X" != j: 24 | k = j + j - i 25 | elif i == "X" and j != "X" != k: 26 | i = j + j - k 27 | grid[pos] = i, j, k 28 | grid = map(list, zip(*grid)) 29 | 30 | # Make sure that our grid is still valid 31 | if any(i + k != j + j for table in (grid, zip(*grid)) for i, j, k in table if X not in (i, j, k)): 32 | return 0 33 | 34 | return grid 35 | 36 | def helper(state): 37 | 38 | state = fill(state) 39 | 40 | if state == 0: # Invalid grid. 41 | return 0 42 | 43 | if all(i != "X" for row in state for i in row): 44 | return state 45 | 46 | for row in xrange(3): 47 | if "X" in state[row]: 48 | col = state[row].index("X") 49 | for _ in xrange(10): 50 | state[row][col] = randint(-1000000, 1000000) # Fill a random value 51 | value = helper(state[:]) 52 | if value: 53 | return value 54 | state[row][col] = X 55 | 56 | X = "X" # Used for eval. This is just a nice trick to read the input 57 | for i, j, k in helper([map(eval, raw_input().split()) for i in xrange(3)]): 58 | print i, j, k 59 | -------------------------------------------------------------------------------- /2019/Senior/prettyAveragePrimes.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | const int PRIMES[224] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89,97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409,419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997, 1009, 1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, 1297, 1301, 1303, 1307, 1319, 1321, 1327, 1361, 1367, 1373, 1381, 1399, 1409, 1423}; 6 | 7 | bool isPrime(int n){ 8 | for(int prime : PRIMES){ 9 | if(prime * prime > n) 10 | return true; 11 | if(n % prime == 0) 12 | return false; 13 | } 14 | return true; 15 | } 16 | 17 | int main() { 18 | 19 | cin.sync_with_stdio(0); 20 | cin.tie(0); 21 | 22 | int q, n, a, b; 23 | 24 | cin >> q; 25 | 26 | while(q--){ 27 | cin >> n; 28 | // The average should have an equal distance to both numbers. 29 | for(int diff = (n + 1) & 1; diff < n; diff += 2){ 30 | a = n - diff; 31 | if(isPrime(a)){ 32 | b = n + diff; 33 | if(isPrime(b)){ 34 | cout << a << " " << b << "\n"; 35 | break; 36 | } 37 | } 38 | } 39 | } 40 | 41 | return 0; 42 | 43 | } -------------------------------------------------------------------------------- /2019/Senior/prettyAveragePrimes_Alternative.py: -------------------------------------------------------------------------------- 1 | from sys import stdin 2 | 3 | input = stdin.readline 4 | 5 | MAXN = 2000002 # Make sure the limit is greater than 1 million because the pairs could be greater than N itself. 6 | sieve = [1]*MAXN 7 | sieve[0] = 0 # Cross out 8 | sieve[1] = 0 9 | primes = [2] 10 | 11 | for i in xrange(2, MAXN, 2): 12 | sieve[i] = 0 # Cross out even numbers 13 | 14 | # Classic Sieve of Eratosthenes to precomputer all the primes 15 | 16 | for i in xrange(3, MAXN, 2): 17 | if sieve[i]: 18 | primes.append(i) 19 | for j in xrange(i * i, MAXN, i + i): 20 | sieve[j] = 0 21 | 22 | for i in xrange(int(input())): 23 | n = int(input()) * 2 24 | # If you rearrange the equation, 25 | # 2N = A + B 26 | # We fix point A, then identify B as 2N - A 27 | for a in primes: 28 | if sieve[n - a]: 29 | print a, n - a 30 | break 31 | -------------------------------------------------------------------------------- /2019/Senior/tourism.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace std; 3 | 4 | /* 5 | Finally solved this one. No datastructure bash and runs in O(N). 6 | Making a DP state with O(N) items was quite difficult, but here is mine: 7 | 8 | DP[i] = Maximum value for the first `i` attractions, **keeping the condition that the # of days is minimal**. 9 | 10 | The last part is crucial, and tells us that for day `i` there is a specific number of attractions it must visit in order to keep the condition valid. 11 | This number of attractions is `i % K`. Consider the upper bound of attractions you've already visited, this is K * C, where C is minimal. 12 | The lower bound of attractions you've visited is `i - K`, where the most recent day takes up all K slots. 13 | 14 | This led to my first naive solution, which runs in O(NK) time: 15 | */ 16 | 17 | /* 18 | // O(NK) - passes subtask 2 19 | const int MAXN = 1000006; 20 | long long dp[MAXN], arr[MAXN]; 21 | 22 | int main(){ 23 | cin.sync_with_stdio(0); cin.tie(0); cout.tie(0); 24 | int N, K; cin >> N >> K; 25 | for(int i = 0; i < N; ++i) cin >> arr[i]; 26 | for(int i = 0; i < N; ++i){ 27 | long long v = arr[i]; 28 | for(int j = i; j > i - i % K; --j) v = max(v, arr[j]); 29 | for(int nxt = i - i % K; nxt > max(-1, i - K); --nxt){ 30 | v = max(v, arr[nxt]); 31 | dp[i] = max(dp[i], v + (nxt == 0 ? 0 : dp[nxt - 1])); 32 | } 33 | } 34 | cout << dp[N - 1] << '\n'; 35 | return 0; 36 | } 37 | */ 38 | 39 | /* 40 | From here on out I will attempt to optimize the inner loop out in order to make it run linear time. 41 | The first half of the loop computes the subarray max from `i` to `i - i % K` in O(K) time. 42 | Observation: There are O(K) indices who's left endpoint of the subarray is located at `i - i % K`. 43 | We can loop in buckets of size K, compute the prefix max at each bucket. 44 | 45 | The second half of the loop is trickier and requires another observation: 46 | If DP[i] and DP[i + 1] are in the same bucket, then DP[i] >= DP[i + 1] (except for the special case). 47 | The special case is that when RMQ[i] < RMQ[i + 1] and DP[i + 1] depends on RMQ[i + 1], DP[i] will decrease. 48 | Otherwise we can take care of the inner loop using a pointer and compute DP[i] in amortized constant time for every bucket. 49 | */ 50 | 51 | const int MAXN = 1000006; 52 | long long dp[MAXN], arr[MAXN], rmq[MAXN]; 53 | 54 | int main(){ 55 | cin.sync_with_stdio(0); cin.tie(0); cout.tie(0); 56 | int N, K; cin >> N >> K; 57 | for(int i = 0; i < N; ++i) cin >> arr[i]; 58 | for(int i = 0; i < N; i += K){ 59 | rmq[i] = arr[i]; 60 | for(int j = i + 1; j < min(N, i + K); ++j) rmq[j] = max(rmq[j - 1], arr[j]); 61 | } 62 | for(int j = 0; j < N; j += K){ 63 | long long cmax = arr[j]; 64 | for(int c = 0, i = min(N - 1, j + K - 1); i >= j; --i){ 65 | if(cmax <= rmq[i + 1]) dp[i] = dp[i + 1] - (rmq[i + 1] - rmq[i]); // Special case 66 | else dp[i] = dp[i + 1]; 67 | while(j - c >= max(0, i - K + 1)){ 68 | assert(j >= c); 69 | cmax = max(cmax, arr[j - c]); 70 | dp[i] = max(dp[i], max(rmq[i], cmax) + (j == c ? 0 : dp[j - c - 1])); 71 | ++c; 72 | } 73 | } 74 | } 75 | cout << dp[N - 1] << '\n'; 76 | return 0; 77 | } 78 | -------------------------------------------------------------------------------- /2019/Senior/triangle.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace std; 3 | 4 | /* 5 | 6 | Subtask 1 - Dynamic Programming 7 | 8 | Let dp[sz][row][col] represent the maximum element of the subtriangle of size sz with the tip at [row, col]. 9 | 10 | To calculate each state, you need to have dp[sz - 1][row + 1][col] and dp[sz - 1][row + 1][col + 1]. 11 | 12 | Look at why this is true -- to build a triangle of size X + 1 you need the lower two subtriangles of size X, along with the subtriangle at the tip. 13 | 14 | We can loop through the sizes from 1 to K. 15 | Go through every point of a subtriangle of size K. 16 | Grab the maximum item of the three subtriangles for every point. 17 | 18 | This results in an O(N^2 * K) algorithm with a very good constant, as there are N^2 cells for every iteration of K. 19 | 20 | Full solution - Optimizing the recurrence 21 | 22 | First, observe that K iterations is a lot. If we draw the overlaps of the 3 subtriangles, there are a lot of overlapping cells! 23 | 24 | If we can find some optimal smaller triangles that overlap less, we can avoid calculating some states of dp[sz], saving time. 25 | 26 | As a matter of fact, we can. The optimal triangle size is CEIL(sz / 3 * 2), try drawing a triangle and see why (draw an equilateral triangle with 9 mini triangles). 27 | 28 | Alternatively, make *2* dp tables, one for subtriangles with the tip at the top, one for subtriangles with the tip at the bottom. 29 | 30 | We can make the larger triangle with 3 right-side up triangles of sz/2, and 1 upside-down triangle of sz/2. 31 | 32 | Both will save sz/CONSTANT states every step, turning the K loop into a log(K) loop. 33 | 34 | Time complexity: O(N^2 log K). 35 | Space complexity: O(N^2). 36 | 37 | */ 38 | 39 | int n, k, dp[3005][3005]; 40 | 41 | void helper(int sz){ 42 | 43 | if(sz == 1) return; 44 | 45 | int overlap = sz * 2 / 3 + (sz % 3 != 0 && sz > 2); 46 | 47 | helper(overlap); 48 | 49 | for(int K, j = 0; j <= n - sz; ++j) 50 | for(K = 0; K <= j; ++K) 51 | dp[j][K] = max(dp[j][K], max(dp[j + sz - overlap][K], dp[j + sz - overlap][K + sz - overlap])); 52 | 53 | } 54 | 55 | int main(){ 56 | 57 | cin.sync_with_stdio(0); cin.tie(0); cout.tie(0); 58 | 59 | cin >> n >> k; 60 | 61 | //dp[row][col] -> max element at size [row][col] at the top 62 | 63 | for(int j, i = 0; i < n; ++i) 64 | for(j = 0; j <= i; ++j) 65 | cin >> dp[i][j]; 66 | 67 | helper(k); 68 | 69 | long long tot = 0; 70 | 71 | for(int j, i = 0; i <= n - k; ++i) 72 | for(j = 0; j <= i; ++j) 73 | tot += dp[i][j]; 74 | 75 | cout << tot << "\n"; 76 | 77 | return 0; 78 | 79 | } 80 | -------------------------------------------------------------------------------- /2021/Senior/s1.py: -------------------------------------------------------------------------------- 1 | N = int(input()) 2 | arr = list(map(int, input().split())) 3 | brr = list(map(int, input().split())) 4 | 5 | # Split into trapezoids 6 | 7 | area = 0 8 | for i in range(1, N + 1): 9 | area += (arr[i] + arr[i - 1]) * brr[i - 1] / 2.0 10 | 11 | print(area) -------------------------------------------------------------------------------- /2021/Senior/s2.py: -------------------------------------------------------------------------------- 1 | rows = [0] * int(input()) 2 | cols = [0] * int(input()) 3 | 4 | # My approach: 5 | # A cell can be uniquely identified by the x and y coordinate 6 | # We can keep track of those instead 7 | # You can keep track of the parity to determine Gold/Black 8 | 9 | for _ in range(int(input())): 10 | t, id = input().split() 11 | if t == "R": rows[int(id) - 1] ^= 1 12 | else: cols[int(id) - 1] ^= 1 13 | 14 | # A little bit of inclusion-exclusion 15 | # O(N * M) works here too 16 | 17 | A = rows.count(1) 18 | B = cols.count(1) 19 | print(A * len(cols) + B * len(rows) - 2 * A * B) 20 | -------------------------------------------------------------------------------- /2021/Senior/s3.py: -------------------------------------------------------------------------------- 1 | N = int(input()) 2 | people = [list(map(int, input().split())) for i in range(N)] 3 | 4 | def calc_time(x): 5 | tot_time = 0 6 | for pos, w, d in people: 7 | # Already in range? 8 | if abs(pos - x) <= d: 9 | continue 10 | # Left side 11 | if pos < x: 12 | tot_time += ((x - d) - pos) * w 13 | # Right side 14 | else: 15 | tot_time += (pos - (x + d)) * w 16 | return tot_time 17 | 18 | left = 0 19 | right = 10**9 20 | 21 | # Binary search on the slope 22 | while left + 1 < right: 23 | mid = (left + right) // 2 24 | # Slope = change in Y value 25 | y1 = calc_time(mid) 26 | y2 = calc_time(mid + 1) 27 | if y1 >= y2: 28 | left = mid # Still decreasing 29 | else: 30 | right = mid # Otherwise increasing 31 | 32 | print(min(calc_time(left), calc_time(right))) 33 | -------------------------------------------------------------------------------- /2021/Senior/s4.py: -------------------------------------------------------------------------------- 1 | import heapq 2 | from collections import deque 3 | 4 | """ 5 | My approach: 6 | An important observation I used is that the only optimal way to reach node N is by taking the subway first, then going to node N by foot 7 | The answer to each output is min(index + dist[i] for i in perm) 8 | Calculating dist[i] is O(N + M) by reversing the edges and doing BFS from node N 9 | You can maintain the best answer using a multiset (Used a priority queue with lazy deletion due to python's lack of multiset) 10 | """ 11 | 12 | # Input parsing 13 | N, W, D = list(map(int, input().split())) 14 | 15 | # Build adjacency list (But reverse the edge directions) 16 | adj = [[] for i in range(N + 1)] 17 | for _ in range(W): 18 | a, b = list(map(int, input().split())) 19 | adj[b].append(a) 20 | 21 | subway = list(map(int, input().split())) 22 | 23 | # Perform BFS from node N instead 24 | dist = [N + 1] * (N + 1) 25 | dist[N] = 0 26 | queue = deque([N]) 27 | while queue: 28 | node = queue.popleft() 29 | for nxt in adj[node]: 30 | # Still unvisited? 31 | if dist[nxt] == N + 1: 32 | dist[nxt] = dist[node] + 1 33 | queue.append(nxt) 34 | 35 | # Insert everything into the heap 36 | ans = [] 37 | for i in range(N): 38 | heapq.heappush(ans, (i + dist[subway[i]], i, subway[i])) 39 | 40 | output = [] 41 | for _ in range(D): 42 | x, y = list(map(int, input().split())) 43 | x -= 1 44 | y -= 1 45 | subway[x], subway[y] = subway[y], subway[x] 46 | 47 | # Insert the newly updated elements into the heapq 48 | heapq.heappush(ans, (x + dist[subway[x]], x, subway[x])) 49 | heapq.heappush(ans, (y + dist[subway[y]], y, subway[y])) 50 | 51 | # Find the lowest time that is still available 52 | while True: 53 | # ans[0] returns the smallest time (thanks heapq) 54 | time, index, station = ans[0] 55 | # Is this still a valid station (did no swaps affect it) 56 | if subway[index] == station: 57 | output.append(time) 58 | break 59 | # If it is invalid, remove it 60 | heapq.heappop(ans) 61 | 62 | print("\n".join(map(str, output))) -------------------------------------------------------------------------------- /2021/Senior/s5.py: -------------------------------------------------------------------------------- 1 | from sys import stdin 2 | from collections import defaultdict 3 | from math import gcd 4 | input = stdin.readline 5 | GI = lambda: int(input()) 6 | gi = lambda: list(map(int, input().split())) 7 | 8 | # My approach: 9 | # If a subarray has GCD of x, then try setting all [L, R] as x 10 | # For every index, it should be LCM of all intervals present 11 | # I did this using line sweep by index, keeping track of the GCDs using a dictionary 12 | # Since x <= 16, we can use O(16) LCM calculation 13 | # Finally, the array is impossible if GCD(L..R) != X, which I used a sparse table to obtain the GCD 14 | 15 | 16 | N, M = gi() 17 | arr = [[] for i in range(N + 1)] 18 | queries = [] 19 | for i in range(M): 20 | l, r, g = gi() 21 | l -= 1 22 | r -= 1 23 | arr[l].append((0, g)) 24 | arr[r + 1].append((1, g)) 25 | queries.append((l, r, g)) 26 | 27 | lcm = lambda a, b: a * b // gcd(a, b) 28 | 29 | current = defaultdict(int) 30 | output = [] 31 | for i in range(N): 32 | for j, k in arr[i]: 33 | if j == 0: 34 | current[k] += 1 35 | else: 36 | current[k] -= 1 37 | if current[k] == 0: 38 | current.pop(k) 39 | out = 1 40 | for i in current: 41 | out = lcm(out, i) 42 | output.append(out) 43 | 44 | sparse = [list(output)] 45 | i = 1 46 | n = len(output) 47 | while i * 2 <= n: 48 | prev = sparse[-1] 49 | sparse.append([gcd(prev[j], prev[j + i]) for j in range(n - i * 2 + 1)]) 50 | i *= 2 51 | 52 | def query(L, R): 53 | d = (R - L).bit_length() - 1 54 | return gcd(sparse[d][L], sparse[d][R - (1 << d)]) 55 | 56 | for l, r, g in queries: 57 | if query(l, r + 1) != g: 58 | print("Impossible") 59 | break 60 | else: 61 | print(" ".join(map(str, output))) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Canadian Computing Competition 2 | This repository contains solutions to recent problems found in the University of Waterloo's Canadian Computing Competition (CCC). 3 | 4 | Most solutions will be written either in Python 2.7, Python 3.8, or C++11. 5 | 6 | For some problems, I will attempt to point out some next step optimizations that can speed up the solution, but these won't be necessary for the problem itself. 7 | 8 | The following codes and solutions are tested on https://dmoj.ca/ and on the CCC Grader for correctness testing. 9 | 10 | ### Problem Source 11 | https://cemc.math.uwaterloo.ca/contests/computing.html 12 | --------------------------------------------------------------------------------