├── LICENSE ├── Qualification Round ├── cryptopangrams.py ├── cryptopangrams.test-generator.py ├── dat-bae.py ├── dat-bae2.py ├── foregone-solution.py └── you-can-go-your-own-way.py ├── README.md ├── Round 1A ├── alien-rhyme.py ├── golf-gophers.py ├── golf-gophers2-prove.py ├── golf-gophers2.py └── pylons.py ├── Round 1B ├── draupnir.py ├── fair-fight.py ├── fair-fight.test-generator.py ├── fair-fight2.py └── manhattan-crepe-cart.py ├── Round 1C ├── bacterial-tactics.py ├── power-arrangers.py └── robot-programming-strategy.py ├── Round 2 ├── contransmutation.py ├── new-elements-part-1.py ├── new-elements-part-2.py ├── pottery-lottery.py └── pottery-lottery2.py ├── Round 3 ├── datacenter-duplex.py ├── napkin-folding.py ├── pancake-pyramid.py ├── zillionim.py └── zillionim2.py ├── World Finals ├── board-meeting.py ├── go-to-considered-helpful.cpp ├── go-to-considered-helpful.py ├── juggle-struggle-part1.py ├── juggle-struggle-part2.py ├── sorting-permutation-unit.py └── wont-sum-must-now.py └── cpplint.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 kamyu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Qualification Round/cryptopangrams.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2019 Qualification Round - Problem C. Cryptopangrams 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000051705/000000000008830b 5 | # 6 | # Time: O(LlogN) 7 | # Space: O(1) 8 | # 9 | 10 | def gcd(a, b): 11 | while b: 12 | a, b = b, a % b 13 | return a 14 | 15 | def cryptopangrams(): 16 | N, L = map(int, raw_input().strip().split()) 17 | MSG = map(int, raw_input().strip().split()) 18 | 19 | primes = set() 20 | for i in xrange(L-1): 21 | if MSG[i] == MSG[i+1]: 22 | continue 23 | p = gcd(MSG[i], MSG[i+1]) 24 | primes.add(p) 25 | primes.add(MSG[i]//p) 26 | primes.add(MSG[i+1]//p) 27 | if len(primes) == 26: 28 | break 29 | 30 | lookup = {} 31 | sorted_primes = sorted(primes) 32 | for i, p in enumerate(sorted_primes): 33 | lookup[p] = chr(ord('A')+i) 34 | 35 | for p in sorted_primes: 36 | result = [lookup[p]] 37 | for i in xrange(L): 38 | if MSG[i] % p != 0: 39 | break 40 | p = MSG[i]//p 41 | result.append(lookup[p]) 42 | else: 43 | return "".join(result) 44 | return "" # never reach 45 | 46 | for case in xrange(input()): 47 | print 'Case #%d: %s' % (case+1, cryptopangrams()) 48 | -------------------------------------------------------------------------------- /Qualification Round/cryptopangrams.test-generator.py: -------------------------------------------------------------------------------- 1 | import random 2 | import sys 3 | 4 | def GetPrimes(n): 5 | primes = [] 6 | is_prime = [True] * n 7 | for i in xrange(3, n, 2): 8 | if not is_prime[i]: 9 | continue 10 | primes.append(i) 11 | for j in xrange(i*i, n, 2*i): 12 | is_prime[j] = False 13 | return primes 14 | 15 | T = 100 16 | N = 10000 17 | PRIMES = GetPrimes(N+1) 18 | 19 | print T 20 | for t in xrange(T): 21 | TXT = [] 22 | L = random.randint(25, 100) 23 | random.shuffle(PRIMES) 24 | primes = PRIMES[:26] 25 | primes.sort() 26 | for i in xrange(26): 27 | TXT.append(i) 28 | while len(TXT) != L+1: 29 | TXT.append(random.randint(0, 25)) 30 | random.shuffle(TXT) 31 | result = [] 32 | p = primes[TXT[0]] 33 | for i in xrange(1, len(TXT)): 34 | result.append(p * primes[TXT[i]]) 35 | p = primes[TXT[i]] 36 | print N, L 37 | print " ".join(map(str, result)) 38 | # print >> sys.stderr, primes 39 | print >> sys.stderr, "Case #{}:".format(t+1), "".join(map(lambda x : chr(ord('A')+x), TXT)) 40 | -------------------------------------------------------------------------------- /Qualification Round/dat-bae.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2019 Qualification Round - Problem D. Dat Bae 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000051705/00000000000881de 5 | # 6 | # Time: O(NlogB) 7 | # Space: O(N) 8 | # 9 | 10 | import sys 11 | 12 | def dat_bae(): 13 | N, B, F = map(int, raw_input().strip().split()) 14 | 15 | # find the smallest Q s.t. 2**Q > B 16 | # p.s. if 2**Q <= B, when the whole 2**Q block is missing, 17 | # we cannot tell which block is lost 18 | Q = B.bit_length() # floor(log2(B))+1 19 | assert(2**Q > B and Q <= F) 20 | 21 | idxs = [0]*(N-B) 22 | for j in xrange(Q): # floor(log2(B)) + 1 times 23 | query = [((i%(2**Q))>>j)&1 for i in xrange(N)] 24 | print "".join(map(str, query)) 25 | sys.stdout.flush() 26 | response = map(int, raw_input()) 27 | for i in xrange(len(response)): 28 | idxs[i] |= (response[i])< total: 37 | used_valid, i = callback(i, flip, total, total) 38 | cnt -= total 39 | flip ^= 1 40 | used_valid, i = callback(i, flip, cnt, cnt) 41 | 42 | def codec(blocks, callback): 43 | i = 0 44 | is_done = True 45 | for total, valid in blocks: 46 | if total == valid or valid == 0: 47 | used_valid, i = callback(i, 0, total, valid) 48 | else: 49 | # equally split each block into 2 blocks. 50 | # after ceil(log2(B)) times splits, 51 | # each block must converge into size 1 or stop split 52 | is_done = False 53 | used_valid, i = callback(i, 0, total//2, valid) 54 | used_valid, i = callback(i, 1, (total+1)//2, valid-used_valid) 55 | return is_done 56 | 57 | def dat_bae(): 58 | N, B, F = map(int, raw_input().strip().split()) 59 | 60 | # ceil(log2(B)) + 1 <= F 61 | # => B <= min(15, N-1) 62 | # => ceil(log2(B)) + 1 <= 5 = F 63 | 64 | # find the smallest Q s.t. 2**Q >= B 65 | # p.s. if 2**Q < B, when the whole 2**Q block is missing, 66 | # we cannot tell which block is lost 67 | Q = (B-1).bit_length() # ceil(log2(B)) 68 | assert(2**Q >= B and Q+1<=F) 69 | # if 2**Q == N, in order to save a query, 70 | # we can just skip init_codec and begin with block (N, N-B) 71 | blocks = [] if 2**Q < N else [(N, N-B)] 72 | while Q >= 0: # at most ceil(log2(B)) + 1 times 73 | query = [] 74 | query_callback = functools.partial(encode, query) 75 | if not blocks: 76 | init_codec(N, 2**Q, query_callback) 77 | else: 78 | is_done = codec(blocks, query_callback) 79 | if is_done: break 80 | 81 | print "".join(query) 82 | sys.stdout.flush() 83 | response = map(int, raw_input()) 84 | 85 | next_blocks = [] 86 | response_callback = functools.partial(decode, response, next_blocks) 87 | if not blocks: 88 | init_codec(N, 2**Q, response_callback) 89 | else: 90 | codec(blocks, response_callback) 91 | blocks, next_blocks = next_blocks, blocks 92 | 93 | # print >> sys.stderr, blocks 94 | Q -= 1 95 | 96 | result, i = [], 0 97 | for total, valid in blocks: 98 | if valid == 0: 99 | for j in xrange(i, i+total): 100 | result.append(str(j)) 101 | i += total 102 | 103 | print " ".join(result) 104 | sys.stdout.flush() 105 | verdict = input() 106 | if verdict == -1: # error 107 | exit() 108 | 109 | for case in xrange(input()): 110 | dat_bae() 111 | -------------------------------------------------------------------------------- /Qualification Round/foregone-solution.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2019 Qualification Round - Problem A. Foregone Solution 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000051705/0000000000088231 5 | # 6 | # Time: O(logN) 7 | # Space: O(1) 8 | # 9 | 10 | def foregone_solution(): 11 | N = raw_input() 12 | A, B = [], [] 13 | for d in N: 14 | if d == '4': 15 | A.append('2') 16 | B.append('2') 17 | else: 18 | A.append(d) 19 | if B: B.append('0') 20 | return "{} {}".format("".join(A), "".join(B)) 21 | 22 | for case in xrange(input()): 23 | print 'Case #%d: %s' % (case+1, foregone_solution()) 24 | -------------------------------------------------------------------------------- /Qualification Round/you-can-go-your-own-way.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2019 Qualification Round - Problem B. You Can Go Your Own Way 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000051705/00000000000881da 5 | # 6 | # Time: O(N) 7 | # Space: O(1) 8 | # 9 | 10 | def you_can_go_your_own_way(): 11 | N = input() 12 | P = raw_input() 13 | result = [] 14 | for move in P: 15 | result.append('E' if move == 'S' else 'S') 16 | return "".join(result) 17 | 18 | for case in xrange(input()): 19 | print 'Case #%d: %s' % (case+1, you_can_go_your_own_way()) 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [GoogleCodeJam 2019](https://codingcompetitions.withgoogle.com/codejam/archive/2019) ![Language](https://img.shields.io/badge/language-Python-orange.svg) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) ![Progress](https://img.shields.io/badge/progress-27%20%2F%2027-ff69b4.svg) 2 | 3 | Python solutions of Google Code Jam 2019. Solution begins with `*` means it will get TLE in the largest data set (total computation amount > `10^8`, which is not friendly for Python to solve in 5 ~ 15 seconds). 4 | 5 | * [Code Jam 2018](https://github.com/kamyu104/GoogleCodeJam-2018) 6 | * [Qualification Round](https://github.com/kamyu104/GoogleCodeJam-2019#qualification-round) 7 | * [Round 1A](https://github.com/kamyu104/GoogleCodeJam-2019#round-1a) 8 | * [Round 1B](https://github.com/kamyu104/GoogleCodeJam-2019#round-1b) 9 | * [Round 1C](https://github.com/kamyu104/GoogleCodeJam-2019#round-1c) 10 | * [Round 2](https://github.com/kamyu104/GoogleCodeJam-2019#round-2) 11 | * [Round 3](https://github.com/kamyu104/GoogleCodeJam-2019#round-3) 12 | * [World Finals](https://github.com/kamyu104/GoogleCodeJam-2019#world-finals) 13 | * [Code Jam 2020](https://github.com/kamyu104/GoogleCodeJam-2020) 14 | 15 | ## Qualification Round 16 | | # | Title | Solution | Time | Space | Difficulty | Tag | Note | 17 | |---| ----- | -------- | ---- | ----- | ---------- | --- | ---- | 18 | |A| [Foregone Solution](https://codingcompetitions.withgoogle.com/codejam/round/0000000000051705/0000000000088231)| [Python](./Qualification%20Round/foregone-solution.py)| _O(logN)_ | _O(1)_ | Easy | | Math | 19 | |B| [You Can Go Your Own Way](https://codingcompetitions.withgoogle.com/codejam/round/0000000000051705/00000000000881da)| [Python](./Qualification%20Round/you-can-go-your-own-way.py)| _O(N)_ | _O(1)_ | Easy | | String | 20 | |C| [Cryptopangrams](https://codingcompetitions.withgoogle.com/codejam/round/0000000000051705/000000000008830b)| [Python](./Qualification%20Round/cryptopangrams.py)| _O(LlogN)_ | _O(1)_ | Medium | | Math | 21 | |D| [Dat Bae](https://codingcompetitions.withgoogle.com/codejam/round/0000000000051705/00000000000881de)| [Python](./Qualification%20Round/dat-bae.py) [Python](./Qualification%20Round/dat-bae2.py) | _O(NlogB)_ | _O(N)_ | Medium | | Bit Manipulation, BFS | 22 | 23 | ## Round 1A 24 | | # | Title | Solution | Time | Space | Difficulty | Tag | Note | 25 | |---| ----- | -------- | ---- | ----- | ---------- | --- | ---- | 26 | |A| [Pylons](https://codingcompetitions.withgoogle.com/codejam/round/0000000000051635/0000000000104e03)| [Python](./Round%201A/pylons.py)| _O(R * C)_ | _O(1)_ | Medium | | Constructive | 27 | |B| [Golf Gophers](https://codingcompetitions.withgoogle.com/codejam/round/0000000000051635/0000000000104f1a)| [Python](./Round%201A/golf-gophers.py) [Python](./Round%201A/golf-gophers2.py) | _O(B * N + BlogM)_ | _O(B)_ | Medium | | Chinese Remainder Theorem | 28 | |C| [Alien Rhyme](https://codingcompetitions.withgoogle.com/codejam/round/0000000000051635/0000000000104e05)| [Python](./Round%201A/alien-rhyme.py)| _O(T)_ | _O(T)_ | Easy | | Trie | 29 | 30 | ## Round 1B 31 | | # | Title | Solution | Time | Space | Difficulty | Tag | Note | 32 | |---| ----- | -------- | ---- | ----- | ---------- | --- | ---- | 33 | |A| [Manhattan Crepe Cart](https://codingcompetitions.withgoogle.com/codejam/round/0000000000051706/000000000012295c)| [Python](./Round%201B/manhattan-crepe-cart.py)| _O(PlogP)_ | _O(P)_ | Easy | | Line Sweep | 34 | |B| [Draupnir](https://codingcompetitions.withgoogle.com/codejam/round/0000000000051706/0000000000122837)| [Python](./Round%201B/draupnir.py) | _O(1)_ | _O(1)_ | Medium | | Math | 35 | |C| [Fair Fight](https://codingcompetitions.withgoogle.com/codejam/round/0000000000051706/0000000000122838)| [Python](./Round%201B/fair-fight.py) [PyPy](./Round%201B/fair-fight2.py)| _O(NlogN)_ | _O(N)_ | Hard | | Mono Stack, Binary Search, RMQ | 36 | 37 | ## Round 1C 38 | | # | Title | Solution | Time | Space | Difficulty | Tag | Note | 39 | |---| ----- | -------- | ---- | ----- | ---------- | --- | ---- | 40 | |A| [Robot Programming Strategy](https://codingcompetitions.withgoogle.com/codejam/round/00000000000516b9/0000000000134c90)| [Python](./Round%201C/robot-programming-strategy.py)| _O(A^2)_ | _O(A)_ | Easy | | Greedy | 41 | |B| [Power Arrangers](https://codingcompetitions.withgoogle.com/codejam/round/00000000000516b9/0000000000134e91)| [Python](./Round%201C/power-arrangers.py) | _O(1)_ | _O(1)_ | Easy | | Math | 42 | |C| [Bacterial Tactics](https://codingcompetitions.withgoogle.com/codejam/round/00000000000516b9/0000000000134cdf)| [Python](./Round%201C/bacterial-tactics.py) | _O(R^2 * C^2 * (R + C))_ | _O(R^2 * C^2)_ | Medium | | Sprague–Grundy Theorem | 43 | 44 | ## Round 2 45 | | # | Title | Solution | Time | Space | Difficulty | Tag | Note | 46 | |---| ----- | -------- | ---- | ----- | ---------- | --- | ---- | 47 | |A| [New Elements: Part 1](https://codingcompetitions.withgoogle.com/codejam/round/0000000000051679/0000000000146183)| [Python](./Round%202/new-elements-part-1.py)| _O(N^2 * log(max(C, J)))_ | _O(N^2 * log(max(C, J)))_ | Easy | | Math | 48 | |B| [Pottery Lottery](https://codingcompetitions.withgoogle.com/codejam/round/0000000000051679/00000000001461c8)| [Python](./Round%202/pottery-lottery.py) [Python](./Round%202/pottery-lottery2.py) | _O(PlogV)_ | _O(V)_ | Medium | | Math, Greedy | 49 | |C| [New Elements: Part 2](https://codingcompetitions.withgoogle.com/codejam/round/0000000000051679/0000000000146184)| [Python](./Round%202/new-elements-part-2.py)| _O(N^2 * log(max(C, J)))_ | _O(log(max(C, J)))_ | Medium | | Math, Continued Fraction | 50 | |D| [Contransmutation](https://codingcompetitions.withgoogle.com/codejam/round/0000000000051679/0000000000146185)| [Python](./Round%202/contransmutation.py) | _O(M)_ | _O(M)_ | Hard | | Graph, Topological Sort, DP | 51 | 52 | ## Round 3 53 | | # | Title | Solution | Time | Space | Difficulty | Tag | Note | 54 | |---| ----- | -------- | ---- | ----- | ---------- | --- | ---- | 55 | |A| [Zillionim](https://codingcompetitions.withgoogle.com/codejam/round/0000000000051707/0000000000158f1a)| [Python](./Round%203/zillionim.py) [Python](./Round%203/zillionim2.py) | _O(R^2)_ | _O(R)_ | Easy | | Sprague-Grundy Theorem, Nim | 56 | |B| [Pancake Pyramid](https://codingcompetitions.withgoogle.com/codejam/round/0000000000051707/00000000001591be)| [Python](./Round%203/pancake-pyramid.py) | _O(S)_ | _O(S)_ | Medium | | Mono Stack | 57 | |C| [Datacenter Duplex](https://codingcompetitions.withgoogle.com/codejam/round/0000000000051707/0000000000158f1c)| [Python](./Round%203/datacenter-duplex.py) | _O(R * C)_ | _O(R * C)_ | Medium | | Union Find | 58 | |D| [Napkin Folding](https://codingcompetitions.withgoogle.com/codejam/round/0000000000051707/0000000000159170)| [Python](./Round%203/napkin-folding.py) | _O(N^2 * K^2)_ | _O(N * K^2)_ | Very Hard | ❤️ | Geometry, Sliding Window, Binary Search, BFS, DFS | 59 | 60 | ## World Finals 61 | You can relive the magic of the 2019 Code Jam World Finals by watching the [Live Stream Recording](https://www.youtube.com/watch?v=biyvpvx9I7E) of the competition, problem explanations, interviews with Google and Code Jam engineers, and announcement of winners. 62 | 63 | | # | Title | Solution | Time | Space | Difficulty | Tag | Note | 64 | |---| ----- | -------- | ---- | ----- | ---------- | --- | ---- | 65 | |A| [Board Meeting](https://codingcompetitions.withgoogle.com/codejam/round/0000000000051708/000000000016c77c)| [Python](./World%20Finals/board-meeting.py) | _O(NlogM)_ | _O(N)_ | Medium | | Binary Search, Math | 66 | |B| [Sorting Permutation Unit](https://codingcompetitions.withgoogle.com/codejam/round/0000000000051708/000000000016c77d)| [Python](./World%20Finals/sorting-permutation-unit.py) | _O(K * N^2)_ | _O(N)_ | Medium | | Sort | 67 | |C| [Won't sum? Must now](https://codingcompetitions.withgoogle.com/codejam/round/0000000000051708/000000000016c77e)| [Python](./World%20Finals/wont-sum-must-now.py) | _O(2^(D/2) * D)_ | _O(D)_ | Hard | ❤️ | Backtracking, Arithmetic, Palindrome | 68 | |D| [Juggle Struggle: Part 1](https://codingcompetitions.withgoogle.com/codejam/round/0000000000051708/000000000016c77f)| [PyPy](./World%20Finals/juggle-struggle-part1.py) |_O(NlogN)_ on average | _O(N)_ | Medium | | Geometry, Recursion, Quick Select | 69 | |E| [Juggle Struggle: Part 2](https://codingcompetitions.withgoogle.com/codejam/round/0000000000051708/000000000016c933)| [PyPy](./World%20Finals/juggle-struggle-part2.py) |_O(NlogN)_ | _O(N)_ | Hard | ❤️ | Geometry, Sort, Mono Stack, Convex Hull | 70 | |F| [Go To Considered Helpful](https://codingcompetitions.withgoogle.com/codejam/round/0000000000051708/000000000016c934)| [C++](./World%20Finals/go-to-considered-helpful.cpp) [*PyPy](./World%20Finals/go-to-considered-helpful.py) |_O(N^4)_ | _O(N^2)_ | Medium | | BFS, DP | 71 | -------------------------------------------------------------------------------- /Round 1A/alien-rhyme.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2019 Round 1A - Problem C. Alien Rhyme 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000051635/0000000000104e05 5 | # 6 | # Time: O(T), t is size of trie, may be up to O(N * l), l is the average length of words 7 | # Space: O(T) 8 | # 9 | 10 | import collections 11 | import functools 12 | 13 | def alien_rhyme_helper(node): 14 | no_rhyme_count = sum(alien_rhyme_helper(child) for child in node.itervalues() if child) 15 | if "_end" in node: 16 | no_rhyme_count += 1 17 | if "_root" not in node and no_rhyme_count >= 2: 18 | no_rhyme_count -= 2 19 | return no_rhyme_count 20 | 21 | def alien_rhyme(): 22 | N = input() 23 | Ws = [] 24 | for _ in xrange(N): 25 | W = list(raw_input()) 26 | W.reverse() 27 | Ws.append(W) 28 | 29 | _trie = lambda: collections.defaultdict(_trie) 30 | trie = _trie() 31 | trie.setdefault("_root") 32 | for W in Ws: 33 | functools.reduce(dict.__getitem__, W, trie).setdefault("_end") 34 | return N - alien_rhyme_helper(trie) 35 | 36 | for case in xrange(input()): 37 | print 'Case #%d: %s' % (case+1, alien_rhyme()) 38 | -------------------------------------------------------------------------------- /Round 1A/golf-gophers.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2019 Round 1A - Problem B. Golf Gophers 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000051635/0000000000104f1a 5 | # 6 | # Time: O(B * N + BlogM) 7 | # Space: O(B) 8 | # 9 | 10 | import sys 11 | import itertools 12 | 13 | def mul_inv(a, b): 14 | b0 = b 15 | x0, x1 = 0, 1 16 | if b == 1: return 1 17 | while a > 1: 18 | q = a // b 19 | a, b = b, a%b 20 | x0, x1 = x1 - q * x0, x0 21 | if x1 < 0: x1 += b0 22 | return x1 23 | 24 | # https://rosettacode.org/wiki/Chinese_remainder_theorem 25 | def chinese_remainder(n, a): # Time: O(BlogM), len(n) = B, PI(n) = M 26 | sum = 0 27 | prod = reduce(lambda a, b: a*b, n) 28 | 29 | for n_i, a_i in itertools.izip(n, a): 30 | p = prod // n_i 31 | sum += a_i * mul_inv(p, n_i) * p 32 | return sum % prod 33 | 34 | def golf_gophers(N, M): 35 | modulis, residues = [], [] 36 | prod = 1 37 | for n_i in reversed(MODULIS): 38 | print " ".join(map(str, [n_i]*B)) 39 | sys.stdout.flush() 40 | modulis.append(n_i) 41 | residues.append(sum(map(int, raw_input().strip().split())) % n_i) 42 | prod *= n_i 43 | if prod >= M: 44 | break 45 | 46 | # these modulis work in chinese remainder theorem (each one is prime to the others) 47 | result = chinese_remainder(modulis, residues) 48 | if result == 0: 49 | result = prod 50 | print result 51 | sys.stdout.flush() 52 | verdict = input() 53 | if verdict == -1: # error 54 | exit() 55 | 56 | def getPrimes(n): 57 | primes = [] 58 | if n < 2: 59 | return [] 60 | 61 | primes.append(2) 62 | is_prime = [True] * n 63 | for i in xrange(3, n+1, 2): 64 | if not is_prime[i-1]: 65 | continue 66 | primes.append(i) 67 | for j in xrange(i*i, n+1, 2*i): 68 | is_prime[j-1] = False 69 | return primes 70 | 71 | B = 18 72 | primes = getPrimes(B) 73 | MODULIS = [] 74 | for i in xrange(len(primes)): # Time: O(BlogB) 75 | moduli = 1 76 | while moduli * primes[i] <= B: 77 | moduli *= primes[i] 78 | MODULIS.append(moduli) 79 | MODULIS.sort() # [5, 7, 9, 11, 13, 16, 17] 80 | 81 | T, N, M = map(int, raw_input().strip().split()) 82 | for case in xrange(T): 83 | golf_gophers(N, M) 84 | -------------------------------------------------------------------------------- /Round 1A/golf-gophers2-prove.py: -------------------------------------------------------------------------------- 1 | modulis = [18, 17, 16, 15, 14, 13, 12] 2 | # modulis = [5, 7, 9, 11, 13, 16, 17] 3 | prod = reduce(lambda a, b: a*b, modulis) 4 | lookup = {} 5 | for m in xrange(1, prod+1): 6 | residues = " ".join(map(str, [m % moduli for moduli in modulis])) 7 | if residues in lookup: 8 | print "not proved: residues are only unique if M <= {} in modulis of {}".format(m-lookup[residues], modulis) 9 | break 10 | lookup[residues] = m 11 | else: 12 | print "proved: residues are all unique if M <= PI({}) = {}".format(modulis, prod) 13 | -------------------------------------------------------------------------------- /Round 1A/golf-gophers2.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2019 Round 1A - Problem B. Golf Gophers 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000051635/0000000000104f1a 5 | # 6 | # Time: O(B * (N + M)) 7 | # Space: O(B) 8 | # 9 | 10 | import sys 11 | 12 | def golf_gophers(N, M): 13 | modulis, residues = [], [] 14 | for b in reversed(xrange(max(2, B-N+1), B+1)): 15 | print " ".join(map(str, [b]*B)) 16 | sys.stdout.flush() 17 | modulis.append(b) 18 | residues.append(sum(map(int, raw_input().strip().split())) % b) 19 | 20 | # these modulis won't work in chinese remainder theorem (because each one is not prime to the others), 21 | # but residues are still unique if M <= 1113840 in modulis of [18, 17, 16, 15, 14, 13, 12]. 22 | # see golf-gophers2-prove.py 23 | for m in xrange(1, M+1): 24 | for i, residue in enumerate(residues): 25 | if m % modulis[i] != residue: 26 | break 27 | else: 28 | print m 29 | sys.stdout.flush() 30 | verdict = input() 31 | if verdict == -1: # error 32 | exit() 33 | break 34 | 35 | B = 18 36 | T, N, M = map(int, raw_input().strip().split()) 37 | for case in xrange(T): 38 | golf_gophers(N, M) 39 | -------------------------------------------------------------------------------- /Round 1A/pylons.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2019 Round 1A - Problem A. Pylons 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000051635/0000000000104e03 5 | # 6 | # Time: O(R * C) 7 | # Space: O(1) 8 | # 9 | 10 | def begin_at_i_seq(C, i): 11 | for c in xrange(i, C+1): 12 | yield c 13 | for c in xrange(1, i): 14 | yield c 15 | 16 | def pylons2(): 17 | R, C = map(int, raw_input().strip().split()) 18 | 19 | swapped = False 20 | if R > C: 21 | R, C = C, R 22 | swapped = True 23 | 24 | result = [] 25 | r = 0 26 | if C >= 4: 27 | while (R-r) >= 3 and (R-r) != 4: # case 3 rows 28 | iter3 = begin_at_i_seq(C, 1) 29 | iter2 = begin_at_i_seq(C, 3) 30 | iter1 = begin_at_i_seq(C, 1) 31 | for c in xrange(C): 32 | result.append((r+3, next(iter3))) 33 | result.append((r+2, next(iter2))) 34 | result.append((r+1, next(iter1))) 35 | r += 3 36 | 37 | if (R-r) == 4: # case 4 rows 38 | iter4 = begin_at_i_seq(C, 3) 39 | iter3 = begin_at_i_seq(C, 1) 40 | iter2 = begin_at_i_seq(C, 3) 41 | iter1 = begin_at_i_seq(C, 1) 42 | for c in xrange(C): 43 | result.append((r+4, next(iter4))) 44 | result.append((r+3, next(iter3))) 45 | result.append((r+2, next(iter2))) 46 | result.append((r+1, next(iter1))) 47 | if len(result) >= 5 and abs(result[-5][0]-result[-4][0]) == abs(result[-5][1]-result[-4][1]): 48 | result[-4], result[-2] = result[-2], result[-4] 49 | r += 4 50 | elif (R-r) == 2 and C >= 5: # case 2 rows 51 | iter2 = begin_at_i_seq(C, 3) 52 | iter1 = begin_at_i_seq(C, 1) 53 | for _ in xrange(C): 54 | result.append((r+2, next(iter2))) 55 | result.append((r+1, next(iter1))) 56 | r += 2 57 | 58 | if r != R: 59 | return "IMPOSSIBLE" 60 | 61 | if swapped: 62 | swapped = False 63 | for i in xrange(len(result)): 64 | result[i] = (result[i][1], result[i][0]) 65 | R, C = C, R 66 | 67 | assert(R*C == len(result)) 68 | for i in xrange(1, len(result)): 69 | assert((abs(result[i][0]-result[i-1][0]) != abs(result[i][1]-result[i-1][1]) and 70 | result[i][0]-result[i-1][0] != 0 and result[i][1]-result[i-1][1] != 0)) 71 | 72 | return "POSSIBLE\n{}".format("\n".join(map(lambda x: " ".join(map(str, x)), result))) 73 | 74 | def pylons(): 75 | R, C = map(int, raw_input().strip().split()) 76 | 77 | swapped = False 78 | if R > C: 79 | R, C = C, R 80 | swapped = True 81 | 82 | result = [] 83 | r = 0 84 | if C >= 4: 85 | while (R-r) >= 4 and (R-r) != 5: # case 4 rows 86 | iter4 = begin_at_i_seq(C, 3) 87 | iter3 = begin_at_i_seq(C, 1) 88 | iter2 = begin_at_i_seq(C, 3) 89 | iter1 = begin_at_i_seq(C, 1) 90 | for c in xrange(C): 91 | result.append((r+4, next(iter4))) 92 | result.append((r+3, next(iter3))) 93 | result.append((r+2, next(iter2))) 94 | result.append((r+1, next(iter1))) 95 | if len(result) >= 5 and abs(result[-5][0]-result[-4][0]) == abs(result[-5][1]-result[-4][1]): 96 | result[-4], result[-2] = result[-2], result[-4] 97 | r += 4 98 | 99 | while (R-r) >= 3: # case 3 rows 100 | iter3 = begin_at_i_seq(C, 1) 101 | iter2 = begin_at_i_seq(C, 3) 102 | iter1 = begin_at_i_seq(C, 1) 103 | for c in xrange(C): 104 | result.append((r+3, next(iter3))) 105 | result.append((r+2, next(iter2))) 106 | result.append((r+1, next(iter1))) 107 | r += 3 108 | 109 | if (R-r) == 2 and C >= 5: # case 2 rows 110 | iter2 = begin_at_i_seq(C, 3) 111 | iter1 = begin_at_i_seq(C, 1) 112 | for _ in xrange(C): 113 | result.append((r+2, next(iter2))) 114 | result.append((r+1, next(iter1))) 115 | r += 2 116 | 117 | if r != R: 118 | return "IMPOSSIBLE" 119 | 120 | if swapped: 121 | swapped = False 122 | for i in xrange(len(result)): 123 | result[i] = (result[i][1], result[i][0]) 124 | R, C = C, R 125 | 126 | assert(R*C == len(result)) 127 | for i in xrange(1, len(result)): 128 | assert((abs(result[i][0]-result[i-1][0]) != abs(result[i][1]-result[i-1][1]) and 129 | result[i][0]-result[i-1][0] != 0 and result[i][1]-result[i-1][1] != 0)) 130 | 131 | return "POSSIBLE\n{}".format("\n".join(map(lambda x: " ".join(map(str, x)), result))) 132 | 133 | for case in xrange(input()): 134 | print 'Case #%d: %s' % (case+1, pylons()) 135 | -------------------------------------------------------------------------------- /Round 1B/draupnir.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2019 Round 1B - Problem B. Draupnir 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000051706/0000000000122837 5 | # 6 | # Time: O(1) 7 | # Space: O(1) 8 | # 9 | 10 | import sys 11 | 12 | def draupnir(): 13 | Q = Q1 # 185 14 | print Q 15 | sys.stdout.flush() 16 | N = input() 17 | R6 = (N % 2**(Q//5)) // 2**(Q//6) 18 | R5 = (N % 2**(Q//4)) // 2**(Q//5) 19 | R4 = (N % 2**(Q//3)) // 2**(Q//4) 20 | 21 | Q = Q2 # 38 22 | print Q 23 | sys.stdout.flush() 24 | N = input() 25 | N -= R4 * 2**(Q//4) + R5 * 2**(Q//5) + R6 * 2**(Q//6) 26 | R3 = (N % 2**(Q//2)) // 2**(Q//3) 27 | R2 = (N % 2**(Q//1)) // 2**(Q//2) 28 | R1 = N // 2**(Q//1) 29 | 30 | print R1, R2, R3, R4, R5, R6 31 | sys.stdout.flush() 32 | verdict = input() 33 | if verdict == -1: # error 34 | exit() 35 | 36 | R = 100 37 | P = 63 38 | Q = 1 39 | floor_log2_R = R.bit_length()-1 # ceil_log2_R = (R-1).bit_length() 40 | while (Q//2) <= floor_log2_R + (Q//3): # while 2**(Q//2) <= R * 2**(Q//3): 41 | Q +=1 42 | assert(2**(Q//2) > R * 2**(Q//3) and 43 | R * 2**(Q//1) < 2**P) 44 | Q2 = Q 45 | while (Q//5) <= floor_log2_R + (Q//6): # while 2**(Q//5) <= R * 2**(Q//6): 46 | Q +=1 47 | assert(2**(Q//5) > R * 2**(Q//6) and 48 | R * 2**(Q//4) < 2**P) 49 | Q1 = Q 50 | T, W = map(int, raw_input().strip().split()) 51 | for case in xrange(T): 52 | draupnir() 53 | -------------------------------------------------------------------------------- /Round 1B/fair-fight.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2019 Round 1B - Problem C. Fair Fight 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000051706/0000000000122838 5 | # 6 | # Time: O(NlogN) 7 | # Space: O(N) 8 | # 9 | 10 | def update_descending_stack(A, curr_max_idxs, i, keep_the_same=False): 11 | while curr_max_idxs and A[curr_max_idxs[-1]] <= A[i]-int(keep_the_same): 12 | curr_max_idxs.pop() 13 | curr_max_idxs.append(i) 14 | 15 | def lower_bound(A, curr_max_idxs, target): 16 | left, right = 0, len(curr_max_idxs)-1 17 | while left <= right: 18 | mid = left + (right-left)//2 19 | if A[curr_max_idxs[mid]] <= target: 20 | right = mid-1 21 | else: 22 | left = mid+1 23 | return left 24 | 25 | def fair_fight(): 26 | N, K = map(int, raw_input().strip().split()) 27 | C = map(int, raw_input().strip().split()) 28 | D = map(int, raw_input().strip().split()) 29 | 30 | R_lookup = [] 31 | C_curr_max_idxs, D_curr_max_idxs = [], [] # descending stack 32 | for i in reversed(xrange(N)): 33 | update_descending_stack(C, C_curr_max_idxs, i) 34 | update_descending_stack(D, D_curr_max_idxs, i) 35 | 36 | if D[i]-C[i] > K: # skip impossible intervals to save time and space 37 | continue 38 | D_R_good_it = lower_bound(D, D_curr_max_idxs, C[i]+K) 39 | D_R_bad_it = lower_bound(D, D_curr_max_idxs, C[i]-K-1) 40 | D_R_good = D_curr_max_idxs[D_R_good_it-1]-1 if D_R_good_it >= 1 else N-1 # rightmost idx of max_D s.t. max_D-Ci <= K 41 | D_R_bad = D_curr_max_idxs[D_R_bad_it-1]-1 if D_R_bad_it >= 1 else N-1 # rightmost idx of max_D s.t. max_D-Ci <= -K-1 42 | C_R = C_curr_max_idxs[-2]-1 if len(C_curr_max_idxs) >= 2 else N-1 # rightmost idx of C s.t. Ci >= C[idx] 43 | R_good, R_bad = min(D_R_good, C_R), min(D_R_bad, C_R) 44 | 45 | R_lookup.append((R_good, R_bad)) 46 | 47 | result = 0 48 | C_curr_max_idxs, D_curr_max_idxs = [], [] # descending stack 49 | for i in xrange(N): 50 | update_descending_stack(C, C_curr_max_idxs, i, True) # keep the idx where C[idx] == Ci 51 | update_descending_stack(D, D_curr_max_idxs, i) 52 | 53 | if D[i]-C[i] > K: # skip impossible intervals to save time and space 54 | continue 55 | D_L_good_it = lower_bound(D, D_curr_max_idxs, C[i]+K) 56 | D_L_bad_it = lower_bound(D, D_curr_max_idxs, C[i]-K-1) 57 | D_L_good = D_curr_max_idxs[D_L_good_it-1]+1 if D_L_good_it >= 1 else 0 # leftmost idx of max_D s.t. max_D-Ci <= K 58 | D_L_bad = D_curr_max_idxs[D_L_bad_it-1]+1 if D_L_bad_it >= 1 else 0 # leftmost idx of max_D s.t. max_D-Ci <= -K-1 59 | C_L = C_curr_max_idxs[-2]+1 if len(C_curr_max_idxs) >= 2 else 0 # leftmost idx of C s.t. C[idx] < Ci 60 | L_good, L_bad = max(D_L_good, C_L), max(D_L_bad, C_L) 61 | 62 | R_good, R_bad = R_lookup.pop() 63 | result += (i-L_good+1)*(R_good-i+1)-(i-L_bad+1)*(R_bad-i+1) 64 | 65 | return result 66 | 67 | for case in xrange(input()): 68 | print 'Case #%d: %s' % (case+1, fair_fight()) 69 | -------------------------------------------------------------------------------- /Round 1B/fair-fight.test-generator.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | B = 100000 4 | N = 100000 5 | T = 10 6 | print T 7 | for _ in xrange(T): 8 | n = random.randint(1, N) 9 | k = random.randint(0, B) 10 | print n, k 11 | C, D = [], [] 12 | for _ in xrange(n): 13 | C.append(random.randint(0, B)) 14 | D.append(random.randint(0, B)) 15 | print " ".join(map(str, C)) 16 | print " ".join(map(str, D)) 17 | -------------------------------------------------------------------------------- /Round 1B/fair-fight2.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2019 Round 1B - Problem C. Fair Fight 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000051706/0000000000122838 5 | # 6 | # Time: O(NlogN), pass in PyPy2 but Python2 7 | # Space: O(NlogN) 8 | # 9 | 10 | import collections 11 | import itertools 12 | 13 | class RangeQuery(object): 14 | def __init__(self, items, fn): 15 | self.__fn = fn 16 | self.__pow = [1] 17 | self.__bit_length = [0] 18 | n, count = len(items), 1 19 | for i in xrange(1, n.bit_length()+1): 20 | self.__pow.append(self.__pow[-1] * 2) 21 | self.__bit_length.extend([i]*min(count, n+1-len(self.__bit_length))) 22 | count *= 2 23 | self.__rq = rq = [[0 for _ in xrange(n.bit_length())] for _ in xrange(n)] 24 | for i in xrange(n): 25 | self.__rq[i][0] = items[i] 26 | for step in xrange(1, n.bit_length()): # Time: O(NlogN) 27 | for i in xrange(n+1-self.__pow[step]): 28 | self.__rq[i][step] = fn(self.__rq[i][step-1], 29 | self.__rq[i+self.__pow[step-1]][step-1]) 30 | 31 | def query(self, start, stop): # Time: O(1) 32 | j = self.__bit_length[stop-start]-1 33 | x = self.__rq[start][j] 34 | y = self.__rq[stop-self.__pow[j]][j] 35 | return self.__fn(x, y) 36 | 37 | def lower_bound(left, right, check): 38 | while left <= right: 39 | mid = left + (right-left)//2 40 | if check(mid): 41 | right = mid-1 42 | else: 43 | left = mid+1 44 | return left 45 | 46 | def upper_bound(left, right, check): 47 | while left <= right: 48 | mid = left + (right-left)//2 49 | if not check(mid): 50 | right = mid-1 51 | else: 52 | left = mid+1 53 | return left # assert(right == left-1) 54 | 55 | def fair_fight(): 56 | N, K = map(int, raw_input().strip().split()) 57 | C = map(int, raw_input().strip().split()) 58 | D = map(int, raw_input().strip().split()) 59 | 60 | C_RMQ, D_RMQ = RangeQuery(C, max), RangeQuery(D, max) 61 | result, next_to_last_seen = 0, collections.defaultdict(int) 62 | for i, (Ci, Di) in enumerate(itertools.izip(C, D)): 63 | if Di-Ci > K: # skip impossible intervals to save time 64 | continue 65 | L_good = lower_bound(next_to_last_seen[Ci], i, 66 | lambda x: C_RMQ.query(x, i+1) == Ci and D_RMQ.query(x, i+1)-Ci <= K) 67 | R_good = upper_bound(i, N-1, 68 | lambda x: C_RMQ.query(i, x+1) == Ci and D_RMQ.query(i, x+1)-Ci <= K)-1 69 | L_bad = lower_bound(next_to_last_seen[Ci], i, 70 | lambda x: C_RMQ.query(x, i+1) == Ci and D_RMQ.query(x, i+1)-Ci <= -K-1) 71 | R_bad = upper_bound(i, N-1, 72 | lambda x: C_RMQ.query(i, x+1) == Ci and D_RMQ.query(i, x+1)-Ci <= -K-1)-1 73 | result += (i-L_good+1)*(R_good-i+1)-(i-L_bad+1)*(R_bad-i+1) 74 | next_to_last_seen[Ci] = i+1 # to avoid duplicated count 75 | 76 | return result 77 | 78 | for case in xrange(input()): 79 | print 'Case #%d: %s' % (case+1, fair_fight()) 80 | -------------------------------------------------------------------------------- /Round 1B/manhattan-crepe-cart.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2019 Round 1B - Problem A. Manhattan Crepe Cart 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000051706/000000000012295c 5 | # 6 | # Time: O(PlogP) 7 | # Space: O(P) 8 | # 9 | 10 | import collections 11 | 12 | def choose(lookup): 13 | positions = list(lookup.iteritems()) 14 | positions.sort() 15 | result, max_votes = None, float("-inf") 16 | curr_pos, curr_votes = 0, 0 17 | for pos, v in positions: 18 | if pos > curr_pos: 19 | if curr_votes > max_votes: 20 | max_votes = curr_votes 21 | result = curr_pos 22 | curr_pos = pos 23 | curr_votes += v 24 | return result 25 | 26 | def manhattan_crepe_cart(): 27 | P, Q = map(int, raw_input().strip().split()) 28 | lookup_X, lookup_Y = collections.defaultdict(int), collections.defaultdict(int) 29 | lookup_X[Q+1], lookup_Y[Q+1] = 0, 0 30 | for _ in xrange(P): 31 | X, Y, D = raw_input().strip().split() 32 | X, Y = int(X), int(Y) 33 | if D == "E": 34 | lookup_X[X+1] += 1 35 | elif D == "W": 36 | lookup_X[X] -= 1 37 | elif D in "N": 38 | lookup_Y[Y+1] += 1 39 | elif D in "S": 40 | lookup_Y[Y] -= 1 41 | 42 | return "{} {}".format(choose(lookup_X), choose(lookup_Y)) 43 | 44 | for case in xrange(input()): 45 | print 'Case #%d: %s' % (case+1, manhattan_crepe_cart()) 46 | -------------------------------------------------------------------------------- /Round 1C/bacterial-tactics.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2019 Round 1C - Problem C. Bacterial Tactics 4 | # https://codingcompetitions.withgoogle.com/codejam/round/00000000000516b9/0000000000134cdf 5 | # 6 | # Time: O(R^2 * C^2 * (R + C)) 7 | # Space: O(R^2 * C^2) 8 | # 9 | 10 | def mex(s): # minimum excludant 11 | excludant = 0 12 | while excludant in s: 13 | excludant += 1 14 | return excludant 15 | 16 | def grundy(RC, r0, c0, r1, c1, lookup): # there are O(R^2 * C^2) subproblems, each costs O(R + C) time 17 | result = 0 18 | if r0 == r1 or c0 == c1: 19 | return 0, result 20 | 21 | if (r0, c0, r1, c1) not in lookup: 22 | s = set() 23 | # horizontal check 24 | for r in xrange(r0, r1): # Time: O(R) 25 | if c0 <= RC[LEFT][r][c0] or RC[RIGHT][r][c0] < c1: # nearest radioactive cell in the same row 26 | continue 27 | g = grundy(RC, r0, c0, r, c1, lookup)[0] ^ \ 28 | grundy(RC, r+1, c0, r1, c1, lookup)[0] 29 | s.add(g) 30 | if not g: # if the opponent loses 31 | result += c1-c0 32 | 33 | # vertical check 34 | for c in xrange(c0, c1): # Time: O(C) 35 | if r0 <= RC[UP][r0][c] or RC[DOWN][r0][c] < r1: # nearest radioactive cell in the same column 36 | continue 37 | g = grundy(RC, r0, c0, r1, c, lookup)[0] ^ \ 38 | grundy(RC, r0, c+1, r1, c1, lookup)[0] 39 | s.add(g) 40 | if not g: # if the opponent loses 41 | result += r1-r0 42 | lookup[r0, c0, r1, c1] = mex(s) # Time: O(R + C) 43 | 44 | return lookup[r0, c0, r1, c1], result 45 | 46 | def bacterial_tactics(): 47 | R, C = map(int, raw_input().strip().split()) 48 | M = [] 49 | for _ in xrange(R): 50 | M.append(list(raw_input().strip())) 51 | 52 | # nearest radioactive cell from left, right, up, down 53 | RC = [[[None for _ in xrange(C)] for _ in xrange(R)] for _ in xrange(4)] 54 | for r in xrange(R): 55 | left_radio = -1 56 | for c in xrange(C): 57 | if M[r][c] == '#': 58 | left_radio = c 59 | RC[LEFT][r][c] = left_radio 60 | right_radio = C 61 | for c in reversed(xrange(C)): 62 | if M[r][c] == '#': 63 | right_radio = c 64 | RC[RIGHT][r][c] = right_radio 65 | for c in xrange(C): 66 | up_radio = -1 67 | for r in xrange(R): 68 | if M[r][c] == '#': 69 | up_radio = r 70 | RC[UP][r][c] = up_radio 71 | down_radio = R 72 | for r in reversed(xrange(R)): 73 | if M[r][c] == '#': 74 | down_radio = r 75 | RC[DOWN][r][c] = down_radio 76 | 77 | g, result = grundy(RC, 0, 0, R, C, {}) 78 | return result if g else 0 79 | 80 | LEFT, RIGHT, UP, DOWN = range(4) 81 | for case in xrange(input()): 82 | print 'Case #%d: %s' % (case+1, bacterial_tactics()) 83 | -------------------------------------------------------------------------------- /Round 1C/power-arrangers.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2019 Round 1C - Problem B. Power Arrangers 4 | # https://codingcompetitions.withgoogle.com/codejam/round/00000000000516b9/0000000000134e91 5 | # 6 | # Time: O(5!-1 + 4!-1 + 3!-1 + 2!-1 = 148) = O(R * R!) 7 | # Space: O(5!-1 + 4!-1 + 3!-1 + 2!-1 = 148) = O(R * R!) 8 | # 9 | 10 | import sys 11 | import collections 12 | import math 13 | 14 | def power_arrangers(): 15 | result, cnt = [], 0 16 | expected_permutation_cnt = R_FAC 17 | Q = range(1, (expected_permutation_cnt-1)*R, R) 18 | for i in reversed(xrange(2, R+1)): 19 | if len(Q) > 1: 20 | lookup = collections.defaultdict(list) 21 | for q in Q: 22 | print q 23 | sys.stdout.flush() 24 | lookup[raw_input()].append(q+1) # inspect the next letter 25 | cnt += 1 26 | expected_permutation_cnt //= len(lookup) 27 | for k, v in lookup.iteritems(): 28 | if len(v) == expected_permutation_cnt-1: # missing this letter in current position 29 | result.append(k) 30 | Q = v 31 | break 32 | else: # only the last 2 letters remain unknown 33 | print Q[0]+1 # inspect the rightmost letter, which is the second letter from the right in the missing set 34 | sys.stdout.flush() 35 | result.append(raw_input()) 36 | cnt += 1 37 | result.append((set(chr(ord('A')+i) for i in xrange(R)) - set(result)).pop()) 38 | 39 | assert(cnt <= F) 40 | print "".join(result) 41 | sys.stdout.flush() 42 | verdict = raw_input() 43 | if verdict == "N": # error 44 | exit() 45 | 46 | R = 5 47 | R_FAC = math.factorial(R) 48 | T, F = map(int, raw_input().strip().split()) 49 | for case in xrange(T): 50 | power_arrangers() 51 | -------------------------------------------------------------------------------- /Round 1C/robot-programming-strategy.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2019 Round 1C - Problem A. Robot Programming Strategy 4 | # https://codingcompetitions.withgoogle.com/codejam/round/00000000000516b9/0000000000134c90 5 | # 6 | # Time: O(A^2) 7 | # Space: O(A) 8 | # 9 | 10 | import collections 11 | 12 | def robot_programming_strategy(): 13 | A = input() 14 | C = [] 15 | for _ in xrange(A): 16 | C.append(raw_input().strip()) 17 | result = [] 18 | C_remains = set(range(A)) 19 | while C_remains: 20 | lookup = collections.defaultdict(list) 21 | for i in C_remains: 22 | lookup[C[i][len(result)%len(C[i])]].append(i) 23 | if len(lookup) == 3: 24 | return "IMPOSSIBLE" 25 | if len(lookup) == 1: 26 | choose = WIN_TO[lookup.keys().pop()] 27 | elif len(lookup) == 2: 28 | choose = LOSE_TO[(CHOICES-set(lookup.iterkeys())).pop()] # choose the one which ties or beats another 29 | for i in lookup[LOSE_TO[choose]]: # remove defeated opponents, otherwise, we may get a wrong result 30 | C_remains.remove(i) 31 | result.append(choose) 32 | return "".join(result) 33 | 34 | CHOICES = set(["S", "R", "P"]) 35 | WIN_TO = {"S" : "R", "R" : "P", "P" : "S"} 36 | LOSE_TO = {"S" : "P", "R" : "S", "P" : "R"} 37 | for case in xrange(input()): 38 | print 'Case #%d: %s' % (case+1, robot_programming_strategy()) 39 | -------------------------------------------------------------------------------- /Round 2/contransmutation.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2019 Round 2 - Problem D. Contransmutation 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000051679/0000000000146185 5 | # 6 | # Time: O(M) 7 | # Space: O(M) 8 | # 9 | 10 | from collections import deque, defaultdict 11 | 12 | def contransmutation(): 13 | M = input() 14 | R = [] 15 | for _ in xrange(M): 16 | R.append(map(lambda x: int(x)-1, raw_input().strip().split())) 17 | G = map(int, raw_input().strip().split()) 18 | 19 | # pre-compute metals which can reach lead 20 | parents = defaultdict(list) 21 | for i in xrange(M): 22 | for child in R[i]: 23 | parents[child].append(i) 24 | M_reach_lead = set([LEAD]) 25 | q = deque([LEAD]) 26 | while q: 27 | i = q.popleft() 28 | for j in parents[i]: 29 | if j in M_reach_lead: 30 | continue 31 | M_reach_lead.add(j) 32 | q.append(j) 33 | 34 | # check if lead is reachable by initial G 35 | R_reach_lead = defaultdict(list) 36 | is_reachable = set() 37 | q = deque() 38 | for i in xrange(M): 39 | if not G[i]: 40 | continue 41 | is_reachable.add(i) 42 | R_reach_lead[i] = [child for child in R[i] if child in M_reach_lead] 43 | q.append(i) 44 | while q: 45 | i = q.popleft() 46 | for j in R_reach_lead[i]: 47 | if j in is_reachable: 48 | continue 49 | is_reachable.add(j) 50 | R_reach_lead[j] = [child for child in R[j] if child in M_reach_lead] 51 | q.append(j) 52 | if LEAD not in is_reachable: # early return if not reachable (optional) 53 | return 0 54 | 55 | # check if lead is bounded for making leads 56 | if R_reach_lead[LEAD]: 57 | curr = LEAD 58 | if len(R_reach_lead[curr])-1 > 0: 59 | return "UNBOUNDED" 60 | curr = R_reach_lead[curr][0] 61 | while curr != LEAD: 62 | if len(R_reach_lead[curr])-1 > 0: 63 | return "UNBOUNDED" 64 | curr = R_reach_lead[curr][0] 65 | R_reach_lead[curr] = [] # exclude lead to try topological sort 66 | 67 | # Kahn's algorithm (topological sort) 68 | indegree = defaultdict(int) 69 | for i in xrange(M): 70 | for j in R_reach_lead[i]: 71 | indegree[j] += 1 72 | dp = list(G) 73 | q = deque([i for i in xrange(M) if i not in indegree]) 74 | while q: 75 | i = q.popleft() 76 | for j in R_reach_lead[i]: 77 | dp[j] += dp[i] 78 | indegree[j] -= 1 79 | if indegree[j] == 0: 80 | indegree.pop(j) 81 | q.append(j) 82 | return "UNBOUNDED" if indegree else dp[LEAD] % MOD 83 | 84 | MOD = 10**9+7 85 | LEAD = 0 86 | for case in xrange(input()): 87 | print 'Case #%d: %s' % (case+1, contransmutation()) 88 | -------------------------------------------------------------------------------- /Round 2/new-elements-part-1.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2019 Round 2 - Problem A. New Elements: Part 1 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000051679/0000000000146183 5 | # 6 | # Time: O(N^2 * log(max(C, J))) 7 | # Space: O(N^2 * log(max(C, J))) 8 | # 9 | 10 | from fractions import Fraction 11 | 12 | def new_elements_part_1(): 13 | N = input() 14 | molecules = [] 15 | for _ in xrange(N): 16 | molecules.append(map(int, raw_input().strip().split())) 17 | 18 | fractions_set = set() 19 | for b in xrange(1, N): 20 | (Cb, Jb) = molecules[b] 21 | for a in xrange(b): 22 | (Ca, Ja) = molecules[a] 23 | # let R = wJ/wC 24 | # => Ca * wC + Ja * R * wC < Cb * wC + Jb * R * wC 25 | # => Ca + Ja * R < Cb + Jb * R 26 | # => (Ca-Cb) < (Jb-Ja) * R 27 | # for each pair if 0 < (Ca-Cb)/(Jb-Ja) < inf 28 | # if we let R = (Ca-Cb)/(Jb-Ja), 29 | # it will map into a unique ordering 30 | if (Ca < Cb and Ja > Jb) or \ 31 | (Ca > Cb and Ja < Jb): 32 | fractions_set.add(Fraction(Ca-Cb, Jb-Ja)) 33 | return len(fractions_set)+1 34 | 35 | for case in xrange(input()): 36 | print 'Case #%d: %s' % (case+1, new_elements_part_1()) 37 | -------------------------------------------------------------------------------- /Round 2/new-elements-part-2.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2019 Round 2 - Problem C. New Elements: Part 2 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000051679/0000000000146184 5 | # 6 | # Time: O(N^2 * log(max(C, J))) 7 | # Space: O(log(max(C, J))) 8 | # 9 | 10 | from fractions import Fraction 11 | 12 | def find_min_fraction_with_min_denominator_between(a, b, d=0): 13 | assert(a < b) 14 | assert(d < 9) 15 | if b-int(a) > 1: 16 | return Fraction(int(a)+1, 1) 17 | if a-int(a) == 0: 18 | return int(a) + Fraction(1, int(1/(b-int(a)))+1) 19 | return int(a) + 1/find_min_fraction_with_min_denominator_between(1/(b-int(a)), 1/(a-int(a)), d+1) 20 | 21 | def new_elements_part_2(): 22 | N = input() 23 | molecules = [] 24 | for _ in xrange(N): 25 | molecules.append(map(int, raw_input().strip().split())) 26 | 27 | L, U = Fraction(0, 1), Fraction(MAX_C_J, 1) 28 | for b in xrange(1, N): 29 | (Cb, Jb) = molecules[b] 30 | for a in xrange(b): 31 | (Ca, Ja) = molecules[a] 32 | if (Ca < Cb and Ja > Jb): 33 | U = min(U, Fraction(Ca-Cb, Jb-Ja)) 34 | elif (Ca > Cb and Ja < Jb): 35 | L = max(L, Fraction(Ca-Cb, Jb-Ja)) 36 | elif (Ca >= Cb) and (Ja >= Jb): 37 | return "IMPOSSIBLE" 38 | if L >= U: 39 | return "IMPOSSIBLE" 40 | 41 | frac = find_min_fraction_with_min_denominator_between(L, U) 42 | return "{} {}".format(frac.denominator, frac.numerator) 43 | 44 | MAX_C_J = 10**9 45 | for case in xrange(input()): 46 | print 'Case #%d: %s' % (case+1, new_elements_part_2()) 47 | -------------------------------------------------------------------------------- /Round 2/pottery-lottery.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2019 Round 2 - Problem B. Pottery Lottery 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000051679/00000000001461c8 5 | # 6 | # Time: O(PlogV) 7 | # Space: O(V) 8 | # 9 | 10 | from sys import stdout 11 | from heapq import heappush, heappop 12 | 13 | def put(i, v, D): 14 | _ = input() 15 | D[0] -= 1 16 | print i+1, v 17 | stdout.flush() 18 | 19 | def pottery_lottery(): 20 | D = [P] 21 | 22 | # day 1 ~ N1 23 | for i in xrange(N1): # sabotage 24 | put(i%S, 1, D) 25 | 26 | # day N1+1 ~ N1+V 27 | min_heap = [] 28 | for i in xrange(V): # inspect 29 | put(i, 0, D) 30 | heappush(min_heap, (len(raw_input().strip().split()), -i)) 31 | 32 | # day N1+V+1 ~ N1+V+N2 33 | candidates = [] 34 | for _ in xrange(C): 35 | candidates.append(-heappop(min_heap)[1]) 36 | for _ in xrange(N2): # sabotage 37 | count, i = heappop(min_heap) 38 | put(-i, 1, D) 39 | heappush(min_heap, (count+1, i)) 40 | 41 | # day N1+V+N2+1 ~ N1+V+N2+C 42 | min_heap = [] 43 | for i in candidates: # inspect 44 | put(i, 0, D) 45 | heappush(min_heap, (len(raw_input().strip().split()), -i)) 46 | 47 | # day N1+V+N2+C+1 ~ P-1 48 | candidate = -heappop(min_heap)[1] 49 | while D[0] > 1: # sabotage 50 | count, i = heappop(min_heap) 51 | put(-i, 1, D) 52 | heappush(min_heap, (count+1, i)) 53 | 54 | # day P 55 | put(candidate, P, D) 56 | 57 | P, V = 100, 20 58 | N1, S, N2, C = 60, 14, 14, 2 # tuned by testing_tool.py (247/250) 59 | assert(N1 + V + N2 + C < P) 60 | for case in xrange(input()): 61 | pottery_lottery() 62 | -------------------------------------------------------------------------------- /Round 2/pottery-lottery2.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2019 Round 2 - Problem B. Pottery Lottery 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000051679/00000000001461c8 5 | # 6 | # Time: O(PlogV) 7 | # Space: O(V) 8 | # 9 | 10 | from sys import stdout 11 | from heapq import heappush, heappop 12 | 13 | def put(i, v, D): 14 | _ = input() 15 | D[0] -= 1 16 | print i+1, v 17 | stdout.flush() 18 | 19 | def pottery_lottery(): 20 | D = [P] 21 | 22 | # day 1 ~ N 23 | for i in xrange(N): # sabotage 24 | put(i%S, 1, D) 25 | 26 | # day N+1 ~ N+V 27 | min_heap = [] 28 | for i in xrange(V): # inspect 29 | put(i, 0, D) 30 | heappush(min_heap, (len(raw_input().strip().split()), -i)) 31 | 32 | # day N+V+1 ~ P-1 33 | candidate = -heappop(min_heap)[1] 34 | while D[0] > 1: # sabotage 35 | count, i = heappop(min_heap) 36 | put(-i, 1, D) 37 | heappush(min_heap, (count+1, i)) 38 | 39 | # day P 40 | put(candidate, P, D) 41 | 42 | P, V = 100, 20 43 | N, S = 60, 14 # tuned by testing_tool.py (243/250) 44 | assert(N + V < P) 45 | for case in xrange(input()): 46 | pottery_lottery() 47 | -------------------------------------------------------------------------------- /Round 3/datacenter-duplex.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2019 Round 3 - Problem C. Datacenter Duplex 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000051707/0000000000158f1c 5 | # 6 | # Time: O(R * C) 7 | # Space: O(R * C) 8 | # 9 | 10 | class UnionFind(object): 11 | def __init__(self, n): 12 | self.set = range(n) 13 | self.count = n 14 | 15 | def find_set(self, x): 16 | if self.set[x] != x: 17 | self.set[x] = self.find_set(self.set[x]) # path compression. 18 | return self.set[x] 19 | 20 | def union_set(self, x, y): 21 | x_root, y_root = map(self.find_set, (x, y)) 22 | if x_root == y_root: 23 | return 24 | self.set[min(x_root, y_root)] = max(x_root, y_root) 25 | self.count -= 1 26 | 27 | def get_id(i, j, C): 28 | return i*C + j 29 | 30 | def datacenter_duplex(): 31 | R, C = map(int, raw_input().strip().split()) 32 | M = [] 33 | for _ in xrange(R): 34 | M.append(list(raw_input().strip())) 35 | 36 | union_find = UnionFind(R*C) 37 | for i in xrange(R): 38 | for j in xrange(C): 39 | if i+1 < R and M[i][j] == M[i+1][j]: 40 | union_find.union_set(get_id(i, j, C), get_id(i+1, j, C)) 41 | if j+1 < C and M[i][j] == M[i][j+1]: 42 | union_find.union_set(get_id(i, j, C), get_id(i, j+1, C)) 43 | 44 | arrangement = [['.' for _ in xrange(C-1)] for _ in xrange(R-1)] 45 | for i in xrange(R-1): 46 | for j in xrange(C-1): 47 | if not (M[i][j] != M[i+1][j] and \ 48 | M[i][j] != M[i][j+1] and \ 49 | M[i][j] == M[i+1][j+1]): 50 | continue 51 | if union_find.find_set(get_id(i, j, C)) != union_find.find_set(get_id(i+1, j+1, C)): 52 | union_find.union_set(union_find.find_set(get_id(i, j, C)), 53 | union_find.find_set(get_id(i+1, j+1, C))) 54 | arrangement[i][j] = '\\' 55 | elif union_find.find_set(get_id(i+1, j, C)) != union_find.find_set(get_id(i, j+1, C)): 56 | union_find.union_set(union_find.find_set(get_id(i+1, j, C)), 57 | union_find.find_set(get_id(i, j+1, C))) 58 | arrangement[i][j] = '/' 59 | 60 | if union_find.count > 2: 61 | return "IMPOSSIBLE" 62 | 63 | result = ["POSSIBLE"] 64 | for row in arrangement: 65 | result.append("".join(row)) 66 | return "\n".join(result) 67 | 68 | for case in xrange(input()): 69 | print 'Case #%d: %s' % (case+1, datacenter_duplex()) 70 | -------------------------------------------------------------------------------- /Round 3/napkin-folding.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2019 Round C - Problem D. Napkin Folding 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000051707/0000000000159170 5 | # 6 | # Time: O(N^2 * K^2), much better than official analysis 7 | # Space: O(N * K^2) 8 | # 9 | 10 | def gcd(a, b): # Time: O((logn)^2) 11 | while b: 12 | a, b = b, a % b 13 | return a 14 | 15 | def advance_polygon_area(p1, p2): 16 | return p1[0]*p2[1]-p1[1]*p2[0] 17 | 18 | def delta_area(a, b, c): 19 | return advance_polygon_area(a, b) + \ 20 | advance_polygon_area(b, c) - \ 21 | advance_polygon_area(a, c) 22 | 23 | def polygon_area(polygon): 24 | area = 0 25 | for i in xrange(len(polygon)): 26 | area += advance_polygon_area(polygon[i-1], polygon[i]) 27 | return area # in this problem, we don't divide the area by 2 and keep it a signed area 28 | 29 | def reflect(P, A, B): # return Q which is the reflection of P over line (A, B) 30 | a, b, c = A[1]-B[1], -(A[0]-B[0]), (A[1]-B[1])*(-A[0])-(A[0]-B[0])*(-A[1]) 31 | if -2 * a * (a * P[0] + b * P[1] + c) % (a * a + b * b) or \ 32 | -2 * b * (a * P[0] + b * P[1] + c) % (a * a + b * b): 33 | return None # in this problem, Q should be integers too, otherwise it won't be on polygon 34 | return (-2 * a * (a * P[0] + b * P[1] + c) // (a * a + b * b) + P[0], 35 | -2 * b * (a * P[0] + b * P[1] + c) // (a * a + b * b) + P[1]) 36 | 37 | def find_candidates(K, lcm): 38 | fractions_set = set() 39 | # 1. a folding point with rational coordinates only has 1 or 3 folding segments (see appendix in official analysis), 40 | # there must be a vertical folding segment, so folding points on an edge would equaully split the edge 41 | # 2. there may be 1 ~ K-1 non-vertex folding points on an edge 42 | for y in xrange(2, K+1): 43 | for x in xrange(1, y): 44 | common = gcd(x, y) 45 | fractions_set.add((x//common, y//common)) 46 | candidates = list(fractions_set) 47 | candidates.sort(key=lambda x : lcm*x[0]//x[1]) # applying binary search should be sorted (full search doesn't matter) 48 | return candidates 49 | 50 | def split(A, B, candidates): 51 | endpoints = [] 52 | for c in candidates: 53 | endpoints.append((A[0]+(B[0]-A[0])*c[0]//c[1], 54 | A[1]+(B[1]-A[1])*c[0]//c[1])) 55 | return endpoints 56 | 57 | def find_possible_endpoints(polygon, candidates): 58 | endpoints = [] 59 | endpoints.append(polygon[0]) 60 | for i in xrange(1, len(polygon)): 61 | endpoints.extend(split(polygon[i-1], polygon[i], candidates)) 62 | endpoints.append(polygon[i]) 63 | endpoints.extend(split(polygon[-1], polygon[0], candidates)) 64 | return endpoints 65 | 66 | def edge_num(begin, end, length, C): 67 | if end < begin: 68 | end += length 69 | begin = begin//C 70 | end = (end-1)//C+1 71 | return (end-begin)+1 72 | 73 | def binary_search(begin, end, C, K, endpoints, total_area, area): 74 | left, right = end+1, end//C*C+C 75 | while left <= right: 76 | mid = left + (right-left)//2 77 | curr_area = area + delta_area(endpoints[end], endpoints[mid%len(endpoints)], endpoints[begin]) 78 | if total_area == K * curr_area: 79 | return mid%len(endpoints) 80 | elif total_area > K * curr_area: 81 | right = mid-1 82 | else: 83 | left = mid+1 84 | return -1 85 | 86 | def find_possible_segments(polygon, K, endpoints): 87 | C = len(endpoints)//len(polygon) # count of polygon and non-polygon vertex on an edge 88 | total_area = polygon_area(polygon) 89 | 90 | begin, end = 0, 0 91 | area = 0 92 | while K*edge_num(begin, end, len(endpoints), C) < len(polygon) + 2*(K-1): 93 | # at most N/K + 2 times becuase a valid pattern forms at least N + 2*(K-1) endpoints 94 | end = (end+C)%len(endpoints) 95 | area += delta_area(endpoints[(end-C)%len(endpoints)], endpoints[end], endpoints[begin]) 96 | 97 | # use sliding window to find the target area 98 | for begin in xrange(len(endpoints)): # O(N*K^2) times 99 | while K*edge_num(begin, end, len(endpoints), C) >= len(polygon) + 2*(K-1): 100 | # at most 3 times (1 invalid + 2 candidate edges) to restore possible end 101 | prev_end = end//C*C if end%C != 0 else (end-C)%len(endpoints) 102 | area -= delta_area(endpoints[prev_end], endpoints[end], endpoints[begin]) 103 | end = prev_end 104 | while K*(edge_num(begin, (end+1)%len(endpoints), len(endpoints), C)) <= len(polygon) + 2*2*(K-1): 105 | # at most 3 times (1 invalid + 2 candidate edges) 106 | # to check because a valid pattern forms at most N + 2*2*(K-1) endpoints 107 | next_end = binary_search(begin, end, C, K, endpoints, total_area, area) # O(log(K^2)) 108 | if next_end == -1: 109 | next_end = (end//C*C+C)%len(endpoints) 110 | area += delta_area(endpoints[end], endpoints[next_end], endpoints[begin]) 111 | end = next_end 112 | if K*area == total_area: # found a candidate end endpoint on the same edge 113 | yield (begin, end) 114 | break # each endpoint has at most one ordered pair to create a line segment, 115 | # and the nearest one is always the only candidate. 116 | # because if this pair is invalid, all other pairs with same begin 117 | # and end with at most one more endpoints are all invalid either. 118 | # this "break" is an optional optimization (doesn't change time complexity). 119 | area -= delta_area(endpoints[end], endpoints[begin], endpoints[(begin+1)%len(endpoints)]) 120 | 121 | def find_pattern(begin, end, length, C): 122 | pattern = [begin] 123 | if end < begin: 124 | end += length 125 | curr = begin//C*C + C 126 | while end-curr > 0: 127 | pattern.append(curr%length) 128 | curr += C 129 | pattern.append(end%length) 130 | return pattern 131 | 132 | def normalize(a, b): 133 | return (a, b) if a <= b else (b, a) 134 | 135 | def is_on_polygon_edge(a, b, length, C): 136 | if a%C == b%C == 0: 137 | return abs(a-b) in (C, length-C) 138 | if a%C == 0: 139 | return a in (b//C*C, (b//C+1)*C%length) 140 | if b%C == 0: 141 | return b in (a//C*C, (a//C+1)*C%length) 142 | return a//C == b//C 143 | 144 | def find_valid_segments(polygon, K, endpoints, endpoints_idx, segment): 145 | C = len(endpoints)//len(polygon) # count of polygon and non-polygon vertex on an edge 146 | 147 | pattern = find_pattern(segment[0], segment[1], len(endpoints), C) # Time: O(N) 148 | segments = set() 149 | stk = [(segment, pattern)] # using queue is also fine (BFS), here we use stack (DFS) 150 | segments.add(normalize(segment[0], segment[1])) 151 | while stk: # Time: O(N + K) 152 | if len(segments) >= K: # only invalid pattern makes more than K-1 segments, this check is not necessary 153 | return None 154 | segment, pattern = stk.pop() 155 | 156 | new_segments, new_pattern = [], [] 157 | for i in xrange(-1, len(pattern)): 158 | p = reflect(endpoints[pattern[i]], endpoints[segment[0]], endpoints[segment[1]]) 159 | if not p or p not in endpoints_idx: # not on polygon 160 | return None 161 | p_idx = endpoints_idx[p] 162 | if new_pattern: 163 | if not is_on_polygon_edge(new_pattern[-1], p_idx, len(endpoints), C): # not on polygon edge 164 | new_segment = normalize(new_pattern[-1], p_idx) 165 | if new_segment not in segments: 166 | new_segments.append(new_segment) 167 | if len(new_pattern) != len(pattern): 168 | new_pattern.append(p_idx) 169 | 170 | for new_segment in new_segments: 171 | stk.append((new_segment, new_pattern)) 172 | segments.add(normalize(new_segment[0], new_segment[1])) 173 | 174 | assert(len(segments) == K-1) # only simple polygon pattern can reach here (no crossed polygon pattern), 175 | # and it must be the answer 176 | return segments 177 | 178 | def to_fraction(p, lcm): 179 | common = gcd(p, lcm) 180 | return "{}/{}".format(p//common, lcm//common) # restore the numbers 181 | 182 | def to_str(p, lcm): 183 | return "{} {}".format(to_fraction(p[0], lcm), to_fraction(p[1], lcm)) 184 | 185 | def napkin_folding(): 186 | N, K = map(int, raw_input().strip().split()) 187 | lcm = 1 188 | for i in xrange(2, K+1): 189 | lcm = lcm * i // gcd(lcm, i) 190 | polygon = [] 191 | for _ in xrange(N): # scale the number by lcm to make sure candidates are also integers 192 | polygon.append(tuple(map(lambda x: int(x)*lcm, raw_input().strip().split()))) 193 | 194 | candidates = find_candidates(K, lcm) # Time: O(K^2 * logK) 195 | endpoints = find_possible_endpoints(polygon, candidates) # Time: O(N * K^2) 196 | endpoints_idx = {v:k for k, v in enumerate(endpoints)} 197 | 198 | for segment in find_possible_segments(polygon, K, endpoints): # Time: O(N * K^2 * logK) 199 | # number of possible segments is at most O(N * K^2) 200 | segments = find_valid_segments(polygon, K, endpoints, endpoints_idx, segment) # Time: O(N + K) 201 | if not segments: 202 | continue 203 | result = ["POSSIBLE"] 204 | for a, b in segments: 205 | result.append("{} {}".format(to_str(endpoints[a], lcm), to_str(endpoints[b], lcm))) 206 | return "\n".join(result) 207 | return "IMPOSSIBLE" 208 | 209 | for case in xrange(input()): 210 | print 'Case #%d: %s' % (case+1, napkin_folding()) 211 | -------------------------------------------------------------------------------- /Round 3/pancake-pyramid.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2019 Round 3 - Problem B. Pancake Pyramid 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000051707/00000000001591be 5 | # 6 | # Time: O(S) 7 | # Space: O(S) 8 | # 9 | 10 | def pancake_pyramid(): 11 | S = input() 12 | P = map(int, raw_input().strip().split()) 13 | 14 | total = 0 15 | stk = [] 16 | for i, p in enumerate(P): 17 | while stk and stk[-1][1] <= p: 18 | _, h = stk.pop() 19 | if not stk: 20 | continue 21 | fill = ((i-1)-stk[-1][0]) * (min(stk[-1][1], p)-h) 22 | left, right = stk[-1][0]-0+1, (S-1)-i+1 23 | total += fill * left * right 24 | stk.append((i, p)) 25 | return total % MOD 26 | 27 | MOD = 10**9+7 28 | for case in xrange(input()): 29 | print 'Case #%d: %s' % (case+1, pancake_pyramid()) 30 | -------------------------------------------------------------------------------- /Round 3/zillionim.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2019 Round 3 - Problem A. Zillionim 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000051707/0000000000158f1a 5 | # 6 | # Time: O(R^2), R is the max number of rounds 7 | # Space: O(R) 8 | # 9 | 10 | # perfect solution by Sprague-Grundy theorem 11 | # (assumed that ai makes a mistake at least once, with at least 1/6 chance by experiments), 12 | 13 | from sys import stdout 14 | 15 | def mex(s): # minimum excludant 16 | excludant = 0 17 | while excludant in s: 18 | excludant += 1 19 | return excludant 20 | 21 | def init_grundy(): # Time: O(R^2) 22 | grundy = [0] 23 | for count in xrange(1, R+1): 24 | s = set() 25 | for i in xrange(count): 26 | s.add(grundy[i] ^ grundy[count-1-i]) 27 | for i in xrange(count-1): 28 | s.add(grundy[i] ^ grundy[count-2-i]) 29 | grundy.append(mex(s)) 30 | return grundy 31 | 32 | def insert_segment(segments, p): 33 | for i in xrange(len(segments)): 34 | start, length = segments[i] 35 | if start <= p <= start+length-1: 36 | segments[i] = (start, p-start) 37 | segments.append((p+L, start+length-(p+L))) 38 | break 39 | segments[:] = [(start, length) for start, length in segments if length >= L] 40 | 41 | def find_segment(segments): # Time: O(R) 42 | g = 0 43 | for _, length in segments: 44 | g ^= grundy[length//L] 45 | if g: 46 | for start, length in segments: 47 | count = length//L # sum(count) <= (R*L)/L = R 48 | for i in xrange(count): 49 | if g ^ grundy[count] ^ grundy[i] ^ grundy[count-1-i] == 0: 50 | return start + i*L 51 | for i in xrange(count-1): 52 | if g ^ grundy[count] ^ grundy[i] ^ grundy[count-2-i] == 0: 53 | l = length%L 54 | mid_length = l + (L-l)//2 55 | return start + i*L + mid_length 56 | assert(False) # impossible to reach here 57 | 58 | return segments[0][0] # if we don't have 100% win rate, 59 | # return a default segment and wait until ai makes any mistake 60 | 61 | def zillionim(): 62 | segments = [(1, R*L)] 63 | while True: # at most R/2 times 64 | P = input() 65 | if P == -2 or P == -3: 66 | break 67 | if P == -1: 68 | exit() 69 | 70 | insert_segment(segments, P) 71 | c = find_segment(segments) # Time: O(R) 72 | print c 73 | stdout.flush() 74 | insert_segment(segments, c) 75 | 76 | R, L = 100, 10**10 77 | grundy = init_grundy() 78 | T, W = map(int, raw_input().strip().split()) 79 | for case in xrange(T): 80 | zillionim() 81 | -------------------------------------------------------------------------------- /Round 3/zillionim2.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2019 Round 3 - Problem A. Zillionim 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000051707/0000000000158f1a 5 | # 6 | # Time: O(R^2), R is the max number of rounds 7 | # Space: O(R) 8 | # 9 | 10 | # 2*10^10 strategy solution 11 | 12 | from sys import stdout 13 | from random import shuffle, seed 14 | 15 | def insert_segment(segments, p): 16 | for i in xrange(len(segments)): 17 | start, length = segments[i] 18 | if start <= p <= start+length-1: 19 | segments[i] = (start, p-start) 20 | segments.append((p+L, start+length-(p+L))) 21 | break 22 | segments[:] = [(start, length) for start, length in segments if length >= L] 23 | 24 | def find_segment(segments): # Time: O(R) 25 | three_or_ups, twos, others = [], [], [] 26 | for p, length in segments: 27 | if length >= 3*L: 28 | three_or_ups.append(p) 29 | elif length == 2*L: 30 | twos.append(p) 31 | else: 32 | others.append(p) 33 | 34 | seed(4) # tuned by testing_tool.py, and it also passed the online judge 35 | map(shuffle, [three_or_ups, twos, others]) 36 | if three_or_ups: 37 | return three_or_ups[0] + 2*L # make more segments in length 2*L as possible 38 | elif others: 39 | return others[0] # break the segments in other lengths to make all segments are in length 2*L 40 | return twos[0] + len(twos)%2 # keep ai in bad even number of segments in length 2*L 41 | 42 | def zillionim(): 43 | segments = [(1, R*L)] 44 | while True: # at most R/2 times 45 | P = input() 46 | if P == -2 or P == -3: 47 | break 48 | if P == -1: 49 | exit() 50 | 51 | insert_segment(segments, P) 52 | c = find_segment(segments) # Time: O(R) 53 | print c 54 | stdout.flush() 55 | insert_segment(segments, c) 56 | 57 | R, L = 100, 10**10 58 | T, W = map(int, raw_input().strip().split()) 59 | for case in xrange(T): 60 | zillionim() 61 | -------------------------------------------------------------------------------- /World Finals/board-meeting.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2019 World Finals - Problem A. Board Meeting 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000051708/000000000016c77c 5 | # 6 | # Time: O(NlogM) 7 | # Space: O(N) 8 | # 9 | 10 | from sys import stdout 11 | 12 | def print_line(s): 13 | print s 14 | stdout.flush() 15 | 16 | def read_line(): 17 | s = raw_input() 18 | if s == "ERROR": 19 | exit() 20 | return s 21 | 22 | def query_diff(u, v): 23 | assert(u%2 == v%2) 24 | print_line("%d %d" % ((u+v)//2, (u-v)//2)) 25 | return int(read_line())*2 # diff_in_U_V = 2 * diff_in_X_Y 26 | 27 | def query_U(N, u, swap=False): 28 | v = 2*M + u%2 29 | if swap: 30 | u, v = v, u 31 | return query_diff(u, v) - N*(u%2) # minus extra diff due to adjustment 32 | 33 | def board_meeting(): 34 | N = (query_diff(2*M+1, 2*M+1) - query_diff(2*M, 2*M)) // 2 35 | U, V = [], [] # u = x + y, v = x - y 36 | for swap, axis in enumerate([U, V]): 37 | for i in xrange(N): 38 | left, right = -2*M, 2*M 39 | while left <= right: 40 | mid = left + (right-left)//2 41 | # find the first mid s.t. its diff is greater than the expected one 42 | if query_U(N, mid+1, swap) - query_U(N, mid, swap) > -N + i*2: 43 | right = mid-1 44 | else: 45 | left = mid+1 46 | axis.append(left) 47 | 48 | print_line("READY") 49 | while True: 50 | s = read_line() 51 | if s == "DONE": 52 | break 53 | x, y = map(int, s.strip().split()) 54 | result = 0 55 | for i in xrange(N): 56 | result += abs((x+y) - U[i]) + abs((x-y) - V[i]) 57 | result //= 2 # diff_in_X_Y = diff_in_U_V / 2 58 | print_line(result) 59 | 60 | T, MAX_N, M, R = map(int, raw_input().strip().split()) 61 | assert(2 + 2 * MAX_N * M.bit_length() * 2 <= R) 62 | for case in xrange(T): 63 | board_meeting() 64 | -------------------------------------------------------------------------------- /World Finals/go-to-considered-helpful.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 kamyu. All rights reserved. 2 | 3 | /* 4 | * Google Code Jam 2019 World Finals - Problem F. Go To Considered Helpful 5 | * https://codingcompetitions.withgoogle.com/codejam/round/0000000000051708/000000000016c934 6 | * 7 | * Time: O(N^4), N is max(R, C) 8 | * Space: O(N^2) 9 | * 10 | */ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | using std::ios_base; 23 | using std::cin; 24 | using std::cout; 25 | using std::vector; 26 | using std::string; 27 | using std::to_string; 28 | using std::queue; 29 | using std::function; 30 | using std::pair; 31 | using std::make_pair; 32 | using std::tie; 33 | using std::max; 34 | using std::min; 35 | 36 | const int MAX_R = 100; 37 | const int MAX_C = 100; 38 | const int INF = MAX_R * MAX_C; 39 | 40 | // Time: O(N^2) 41 | vector> inline bfs(const vector>& A, 42 | const int r, int c, 43 | const function& check_fn) { 44 | const auto& R = A.size(), &C = A[0].size(); 45 | static const vector> directions{{0, 1}, {1, 0}, 46 | {0, -1}, {-1, 0}}; 47 | vector> dist(R, vector(C, INF)); 48 | dist[r][c] = 0; 49 | queue> q({{r, c}}); 50 | while (!q.empty()) { 51 | int r, c; 52 | tie(r, c) = q.front(); q.pop(); 53 | for (const auto& kvp : directions) { 54 | const auto& nr = r + kvp.first, &nc = c + kvp.second; 55 | if (0 <= nr && nr < R && 0 <= nc && nc < C && 56 | dist[nr][nc] == INF && check_fn(nr, nc)) { 57 | dist[nr][nc] = dist[r][c] + 1; 58 | q.emplace(nr, nc); 59 | } 60 | } 61 | } 62 | return dist; 63 | } 64 | 65 | bool inline check(const vector>& A, int r, int c) { 66 | const auto& R = A.size(), &C = A[0].size(); 67 | return 0 <= r && r < R && 68 | 0 <= c && c < C && 69 | A[r][c] != '#'; 70 | } 71 | 72 | string go_to_considered_helpful() { 73 | int R, C; 74 | cin >> R >> C; 75 | vector> A(R, vector(C)); 76 | pair M, N; 77 | for (int r = 0; r < R; ++r) { 78 | for (int c = 0; c < C; ++c) { 79 | cin >> A[r][c]; 80 | if (A[r][c] == 'M') { 81 | M = make_pair(r, c); 82 | } else if (A[r][c] == 'N') { 83 | N = make_pair(r, c); 84 | } 85 | } 86 | } 87 | const auto& P = bfs(A, M.first, M.second, 88 | [&A](int r, int c) { return A[r][c] != '#'; }); 89 | int result = P[N.first][N.second]; 90 | int cnt = 0; 91 | for (int dr = -R + 1; dr < R; ++dr) { // enumerate (dr, dc) 92 | for (int dc = -C + 1; dc < C; ++dc) { 93 | if ((dr == 0 && dc == 0) || 94 | !check(A, N.first - dr, N.second - dc)) { 95 | continue; 96 | } 97 | vector>> is_valid(2, 98 | vector>(R, vector(C))); 99 | for (int r = 0; r < R; ++r) { 100 | for (int c = 0; c < C; ++c) { 101 | is_valid[0][r][c] = check(A, r, c); 102 | } 103 | } 104 | for (int k = 1; 105 | check(A, N.first - dr * k, N.second - dc * k); 106 | ++k) { // enumerate k 107 | // the number of (dr, dc, k) combinations is 108 | // at most sum(N / max(abs(dr), abs(dc))) 109 | // for each (dr, dc) = O(N^2) 110 | assert(++cnt <= 2 * max(R, C) * max(R, C)); 111 | auto& is_valid_for_all_k_loops = is_valid[k % 2]; 112 | auto& is_valid_for_all_k_minus_1_loops = is_valid[(k - 1) % 2]; 113 | for (int r = 0; r < R; ++r) { 114 | for (int c = 0; c < C; ++c) { 115 | is_valid_for_all_k_loops[r][c] = 116 | is_valid_for_all_k_minus_1_loops[r][c] && 117 | check(A, r - dr * k, c - dc * k); 118 | } 119 | } 120 | const auto& Q1 = bfs(A, N.first, N.second, 121 | [&is_valid_for_all_k_loops](int r, int c) { 122 | return is_valid_for_all_k_loops[r][c]; 123 | }); 124 | const auto& Q2 = bfs(A, N.first - dr, N.second - dc, 125 | [&is_valid_for_all_k_minus_1_loops](int r, int c) { 126 | return is_valid_for_all_k_minus_1_loops[r][c]; 127 | }); 128 | for (int r = 0; r < R; ++r) { // enumerate all possible cells B 129 | for (int c = 0; c < C; ++c) { 130 | if (!check(A, r - dr * k, c - dc * k)) { 131 | continue; 132 | } 133 | // instructions: 134 | // M ---P---> B ---Q1---> N ---Q2---> Goto B 135 | result = min(result, 136 | P[r - dr * k][c - dc * k] + 137 | Q1[r][c] + Q2[r][c] + 1); 138 | } 139 | } 140 | } 141 | } 142 | } 143 | return result == INF ? "IMPOSSIBLE" : to_string(result); 144 | } 145 | 146 | int main() { 147 | ios_base::sync_with_stdio(false), cin.tie(nullptr); 148 | int T; 149 | cin >> T; 150 | for (int test = 1; test <= T; ++test) { 151 | cout << "Case #" << test << ": " << go_to_considered_helpful() << '\n'; 152 | } 153 | return 0; 154 | } 155 | -------------------------------------------------------------------------------- /World Finals/go-to-considered-helpful.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2019 World Finals - Problem F. Go To Considered Helpful 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000051708/000000000016c934 5 | # 6 | # Time: O(N^4), N is max(R, C), TLE in test set 2 (PyPy2 TLE, too) 7 | # Space: O(N^2) 8 | # 9 | 10 | from collections import deque 11 | 12 | def bfs(A, r, c, check_fn): # Time: O(N^2) 13 | R, C = len(A), len(A[0]) 14 | dist = [[INF for _ in xrange(C)] for _ in xrange(R)] 15 | dist[r][c] = 0 16 | q = deque([(r, c)]) 17 | while q: 18 | r, c = q.popleft() 19 | for d in DIRECTIONS: 20 | nr, nc = r+d[0], c+d[1] 21 | if 0 <= nr < R and 0 <= nc < C and \ 22 | dist[nr][nc] == INF and check_fn(nr, nc): 23 | dist[nr][nc] = dist[r][c]+1 24 | q.append((nr, nc)) 25 | return dist 26 | 27 | def check(A, r, c): 28 | R, C = len(A), len(A[0]) 29 | return 0 <= r < R and 0 <= c < C and A[r][c] != '#' 30 | 31 | def go_to_considered_helpful(): 32 | R, C = map(int, raw_input().strip().split()) 33 | A = [] 34 | for r in xrange(R): 35 | A.append(list(raw_input().strip())) 36 | for c in xrange(C): 37 | if A[r][c] == 'M': 38 | M = (r, c) 39 | elif A[r][c] == 'N': 40 | N = (r, c) 41 | P = bfs(A, M[0], M[1], lambda r, c: A[r][c] != '#') 42 | result = P[N[0]][N[1]] 43 | cnt = 0 44 | for dr in xrange(-R+1, R): # enumerate (dr, dc) 45 | for dc in xrange(-C+1, C): 46 | if (dr, dc) == (0, 0) or not check(A, N[0]-dr, N[1]-dc): 47 | continue 48 | is_valid = [[[check(A, r, c) if k == 0 else False for c in xrange(C)] for r in xrange(R)] for k in xrange(2)] 49 | k = 1 50 | while check(A, N[0]-dr*k, N[1]-dc*k): # enumerate k 51 | cnt += 1 52 | assert(cnt <= 2*max(R, C)**2) # the number of (dr, dc, k) combinations is 53 | # at most sum(N / max(abs(dr), abs(dc))) 54 | # for each (dr, dc) = O(N^2) 55 | is_valid_for_all_k_loops, is_valid_for_all_k_minus_1_loops = is_valid[k%2], is_valid[(k-1)%2] 56 | for r in xrange(R): 57 | for c in xrange(C): 58 | is_valid_for_all_k_loops[r][c] = is_valid_for_all_k_minus_1_loops[r][c] and check(A, r-dr*k, c-dc*k) 59 | Q1 = bfs(A, N[0], N[1], lambda r, c: is_valid_for_all_k_loops[r][c]) 60 | Q2 = bfs(A, N[0]-dr, N[1]-dc, lambda r, c: is_valid_for_all_k_minus_1_loops[r][c]) 61 | for r in xrange(R): # enumerate all possible cells B 62 | for c in xrange(C): 63 | if not check(A, r-dr*k, c-dc*k): 64 | continue 65 | # instructions: M ---P---> B ---Q1---> N ---Q2---> Goto B 66 | result = min(result, P[r-dr*k][c-dc*k] + Q1[r][c] + Q2[r][c] + 1) 67 | k += 1 68 | return "IMPOSSIBLE" if result == INF else result 69 | 70 | MAX_R, MAX_N = 100, 100 71 | INF = MAX_R*MAX_N 72 | DIRECTIONS = [(0, 1), (1, 0), (0, -1), (-1, 0)] 73 | for case in xrange(input()): 74 | print 'Case #%d: %s' % (case+1, go_to_considered_helpful()) 75 | -------------------------------------------------------------------------------- /World Finals/juggle-struggle-part1.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2019 World Finals - Problem D. Juggle Struggle: Part 1 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000051708/000000000016c77f 5 | # 6 | # Time: O(NlogN) on average, pass in PyPy2 but Python2 7 | # Space: O(N) 8 | # 9 | 10 | from random import randint, seed 11 | 12 | def nth_element(nums, n, compare=lambda a, b: a < b): 13 | def partition_around_pivot(left, right, pivot_idx, nums, compare): 14 | new_pivot_idx = left 15 | nums[pivot_idx], nums[right] = nums[right], nums[pivot_idx] 16 | for i in xrange(left, right): 17 | if compare(nums[i], nums[right]): 18 | nums[i], nums[new_pivot_idx] = nums[new_pivot_idx], nums[i] 19 | new_pivot_idx += 1 20 | 21 | nums[right], nums[new_pivot_idx] = nums[new_pivot_idx], nums[right] 22 | return new_pivot_idx 23 | 24 | left, right = 0, len(nums) - 1 25 | while left <= right: 26 | pivot_idx = randint(left, right) 27 | new_pivot_idx = partition_around_pivot(left, right, pivot_idx, nums, compare) 28 | if new_pivot_idx == n: 29 | return 30 | elif new_pivot_idx > n: 31 | right = new_pivot_idx - 1 32 | else: # new_pivot_idx < n 33 | left = new_pivot_idx + 1 34 | 35 | def area(p, q, r): 36 | return (p[0]-r[0])*(q[1]-r[1]) - (p[1]-r[1])*(q[0]-r[0]) 37 | 38 | def pairing(P, left, right, result): 39 | assert(len(left) == len(right)) 40 | if not left or not right: 41 | return 42 | if len(left) == 1 and len(right) == 1: 43 | result[left[0]], result[right[0]] = right[0], left[0] 44 | return 45 | # to avoid worst case, randomly pick (p, q) to partition points like quick sort 46 | p, q = P[left[randint(0, len(left)-1)]], P[right[randint(0, len(right)-1)]] 47 | points = [] 48 | for i in left: 49 | points.append((area(p, q, P[i]), ~i)) 50 | for i in right: 51 | points.append((area(p, q, P[i]), i)) 52 | mid = len(points)//2 # evenly partitioned by point-line distance, then by left/right area 53 | nth_element(points, mid) 54 | left1, right1, left2, right2 = [], [], [], [] 55 | for i in xrange(mid): 56 | if points[i][1] < 0: # come from left area 57 | left1.append(~points[i][1]) 58 | else: 59 | right2.append(points[i][1]) 60 | for i in xrange(mid, len(points)): 61 | if points[i][1] < 0: # come from left area 62 | left2.append(~points[i][1]) 63 | else: 64 | right1.append(points[i][1]) 65 | pairing(P, left1, right1, result) 66 | pairing(P, left2, right2, result) 67 | 68 | def juggle_struggle_part1(): 69 | N = input() 70 | P = [None]*(2*N) 71 | for i in xrange(len(P)): 72 | P[i] = map(int, raw_input().strip().split()) 73 | 74 | left_bottom = P.index(min(P)) # pick a convex hull point 75 | point_idx = [i for i in xrange(len(P)) if i != left_bottom] 76 | mid = len(point_idx)//2 # no collinear triple points, evenly partitioned by a convex hull point 77 | nth_element(point_idx, mid, lambda a, b: area(P[a], P[b], P[left_bottom]) < 0) 78 | mid_idx = point_idx[mid] 79 | left, right = point_idx[:mid], point_idx[mid+1:] 80 | result = [None]*(2*N) 81 | result[left_bottom], result[mid_idx] = mid_idx, left_bottom 82 | pairing(P, left, right, result) 83 | return " ".join(map(str, map(lambda x: x+1, result))) 84 | 85 | seed(0) 86 | for case in xrange(input()): 87 | print 'Case #%d: %s' % (case+1, juggle_struggle_part1()) 88 | -------------------------------------------------------------------------------- /World Finals/juggle-struggle-part2.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2019 World Finals - Problem E. Juggle Struggle: Part 2 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000051708/000000000016c933 5 | # 6 | # Time: O(NlogN), pass in PyPy2 but Python2 7 | # Space: O(N) 8 | # 9 | 10 | from itertools import islice 11 | 12 | # Compute the cross product of vectors AB and AC 13 | CW, COLLINEAR, CCW = range(-1, 2) 14 | def ccw(A, B, C): 15 | area = (B[0]-A[0])*(C[1]-A[1]) - (B[1]-A[1])*(C[0]-A[0]) 16 | return CCW if area > 0 else CW if area < 0 else COLLINEAR 17 | 18 | # Return true if line segments AB and CD intersect 19 | def intersect(A, B, C, D): 20 | return ccw(A,C,D) != ccw(B,C,D) and ccw(A,B,C) != ccw(A,B,D) 21 | 22 | # Return the intersection of AB and CD if it exists 23 | def find_intersection(A, B, C, D): 24 | (x1, y1), (x2, y2), (x3, y3), (x4, y4) = A, B, C, D 25 | px, py = None, None 26 | detC = (x1-x2)*(y3-y4)-(y1-y2)*(x3-x4) 27 | if detC: 28 | detA, detB = (x1*y2-y1*x2)*(x3-x4), (x3*y4-y3*x4) 29 | px = (detA-(x1-x2)*detB)*1.0/detC 30 | py = (detA-(y1-y2)*detB)*1.0/detC 31 | return (px, py) 32 | 33 | def reflect_across_x(p): 34 | return (p[0], -p[1]) 35 | 36 | def reflect_across_y(p): 37 | return (-p[0], p[1]) 38 | 39 | def find_leftmost_below(L, F): 40 | if not L: 41 | return 42 | 43 | # after removing vertical lines from L, we can sort L by y coordinate at X0 44 | X0 = min(min(l[1][0], l[2][0]) for l in L) 45 | L.sort(reverse=True, key=lambda x: find_intersection(x[1], x[2], (X0, 0), (X0, 1))[1]) 46 | X1 = max(max(l[1][0], l[2][0]) for l in L) 47 | stk = [(0, X1)] 48 | for i, (idx, A, B) in enumerate(islice(L, 1, len(L)), 1): 49 | while True: # enumerate below and above directions to check all possible leftmost intersections 50 | _, C, D = L[stk[-1][0]] 51 | X = find_intersection(A, B, C, D)[0] 52 | if not (X is not None and 53 | min(A[0], B[0]) <= X <= max(A[0], B[0]) and 54 | min(C[0], D[0]) <= X <= max(C[0], D[0])): 55 | F.add(idx) 56 | break 57 | if not (len(stk) >= 2 and stk[-1][1] <= X): 58 | break 59 | stk.pop() 60 | if idx in F: 61 | continue 62 | stk.append((i, X)) # only keep valid X in stk 63 | 64 | def juggle_struggle_part2(): 65 | N = input() 66 | L = [None]*N 67 | V = [] 68 | for i in xrange(len(L)): 69 | X1, Y1, X2, Y2 = map(int, raw_input().strip().split()) 70 | if X1 == X2: 71 | V.append(i) 72 | L[i] = [i, (X1, Y1), (X2, Y2)] 73 | 74 | F = set() 75 | if len(V) > 1: # more than 1 vertical lines 76 | F = set(V) 77 | elif len(V) == 1: # only 1 vertical line 78 | idx, A, B = L[V[0]] 79 | for j, C, D in L: 80 | if j == idx: 81 | continue 82 | if not intersect(A, B, C, D): 83 | F.add(idx) 84 | break 85 | 86 | for leftmost in [True, False]: 87 | for below in [True, False]: 88 | l = [l for l in L if l[0] not in F] 89 | if not leftmost: 90 | l = map(lambda x: [x[0], reflect_across_y(x[1]), reflect_across_y(x[2])], l) 91 | if not below: 92 | l = map(lambda x: [x[0], reflect_across_x(x[1]), reflect_across_x(x[2])], l) 93 | find_leftmost_below(l, F) 94 | 95 | for f in list(F): # at most 25 by limit constraint 96 | i, A, B = L[f] 97 | for idx, C, D in L: 98 | if idx == i or idx in F: 99 | continue 100 | if not intersect(A, B, C, D): 101 | F.add(idx) 102 | assert(len(F) <= MAX_F_SIZE) 103 | result = map(lambda x: x+1, sorted(F)) 104 | return "MAGNIFICENT" if not result else " ".join(map(str, result)) 105 | 106 | MAX_F_SIZE = 25 107 | for case in xrange(input()): 108 | print 'Case #%d: %s' % (case+1, juggle_struggle_part2()) 109 | -------------------------------------------------------------------------------- /World Finals/sorting-permutation-unit.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2019 World Finals - Problem B. Sorting Permutation Unit 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000051708/000000000016c77d 5 | # 6 | # Time: O(K * N^2), each array costs 1.5N + 6N + N = 8.5N operations 7 | # Space: O(N) 8 | # 9 | 10 | def normalize(nums): 11 | A = [(num, i) for i, num in enumerate(nums)] 12 | A.sort() 13 | lookup = {i:rank for rank, (num, i) in enumerate(A)} 14 | return map(lambda x: lookup[x], xrange(len(nums))) 15 | 16 | def rotate(nums, k, n): 17 | def reverse(nums, start, end): 18 | while start < end: 19 | nums[start], nums[end-1] = nums[end-1], nums[start] 20 | start += 1 21 | end -= 1 22 | 23 | k %= n 24 | if k == 0: 25 | return 26 | reverse(nums, 0, n) 27 | reverse(nums, 0, k) 28 | reverse(nums, k, n) 29 | 30 | def rotates(nums, k, seq, shift): 31 | assert(k >= 0) # k should be non-negative rotation count to avoid wrong permutations 32 | shift[0] = (shift[0]+k)%(len(nums)-1) 33 | rotate(nums, k, len(nums)-1) 34 | seq.extend(ROTATIONS[k]) # split k rotations into at most 6 permutations 35 | 36 | def swap(nums, seq): # at most 1.5N swaps 37 | nums[-1], nums[-2] = nums[-2], nums[-1] 38 | seq.append(1) 39 | 40 | def sorting_permutation_unit(): 41 | P, S, K, N = map(int, raw_input().strip().split()) 42 | 43 | perms = [] 44 | perms.append(range(1, N+1)) 45 | perms[-1][-1], perms[-1][-2] = perms[-1][-2], perms[-1][-1] 46 | for r in ROTATE_BY: 47 | if r > N-2: 48 | break 49 | perms.append(range(1, N+1)) 50 | rotate(perms[-1], r, len(perms[-1])-1) 51 | 52 | result = [""] 53 | result.append(str(len(perms))) 54 | for perm in perms: 55 | result.append(" ".join(map(str, perm))) 56 | 57 | for _ in xrange(K): 58 | A = normalize(map(int, raw_input().strip().split())) 59 | seq = [0] 60 | shift = [0] 61 | while True: 62 | # rotate the first N-1 ones into the correct positions and swap(N-1, N) 63 | # until Nth position becomes the largest one, 64 | # at most 6N operations 65 | while A[-1] != len(A)-1: 66 | rotates(A, (len(A)-2) - (shift[0]+A[-1])%(len(A)-1), seq, shift) 67 | swap(A, seq) 68 | # find the nearest incorrect relative position from the last position 69 | for nearest_pos in reversed(xrange(len(A)-1)): 70 | if nearest_pos != (shift[0]+A[nearest_pos])%(len(A)-1): 71 | break 72 | else: 73 | break 74 | # rotate the nearest incorrect one to (N-1)th position and swap(N-1, N), 75 | # at most N operations due to choosing the nearest incorrect one to rotate 76 | # which makes one full cycle rotatation in total 77 | rotates(A, (len(A)-2) - nearest_pos, seq, shift) 78 | swap(A, seq) 79 | # do the final rotations to put them in the correct absolute positions 80 | rotates(A, (len(A)-2) - A.index(len(A)-2), seq, shift) 81 | seq[0] = len(seq)-1 82 | result.append(" ".join(map(str, seq))) 83 | return "\n".join(result) 84 | 85 | MAX_N = 50 86 | ROTATE_BY = [1, 3, 9, 27] 87 | ROTATIONS = [[] for _ in xrange(MAX_N-1)] 88 | for k in xrange(len(ROTATIONS)): 89 | count = 0 90 | r = k 91 | for i in reversed(xrange(len(ROTATE_BY))): 92 | q, r = divmod(r, ROTATE_BY[i]) 93 | ROTATIONS[k].extend([i+2]*q) # 1-based index and index 1 reserved for swap permutation 94 | count += q 95 | assert(count <= 6) # each rotations could be represented as at most 6 permutations 96 | for case in xrange(input()): 97 | print 'Case #%d: %s' % (case+1, sorting_permutation_unit()) 98 | -------------------------------------------------------------------------------- /World Finals/wont-sum-must-now.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2019 World Finals - Problem C. Won't sum? Must now 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000051708/000000000016c77e 5 | # 6 | # Time: O(2^(D/2) * D), D is the number of digits of S 7 | # Space: O(D) 8 | # 9 | 10 | from itertools import imap 11 | 12 | def to_int(x): # list of ints to int 13 | return int("".join(map(str, x))) 14 | 15 | def to_list(X): # int to list of ints 16 | return map(int, list(str(X))) 17 | 18 | def gen_palindromes(S): 19 | # at most 208 times because the smallest palindrome of triples 20 | # is at most 10801 (208-th smallest palindrome) in this problem 21 | l, n = 1, None 22 | while True: 23 | lefts = [""] if n is None else imap(str, xrange(n, 10*n)) 24 | for left in lefts: 25 | mids = [""] if l%2 == 0 else imap(str, xrange(10)) 26 | for mid in mids: 27 | P = int(left + mid + left[::-1]) 28 | if P > S: 29 | return 30 | yield P 31 | if l%2 == 1: 32 | n = 1 if n is None else n*10 33 | l += 1 34 | 35 | def set_digits(x, o, start): 36 | for i in xrange(len(o)): 37 | if x[start+i] is not None and x[start+i] != o[i]: 38 | return False 39 | x[start+i], x[-1-(start+i)] = o[i], o[i] 40 | return True 41 | 42 | def clear_digits(x, o, start): 43 | for i in xrange(len(o)): 44 | x[start+i], x[-1-(start+i)] = None, None 45 | 46 | def find_pair_with_same_length(s, x, y, start, left_carry, right_carry): 47 | def gen_X_Y(): 48 | for Y in xrange(max(target-9, 0), min(target+1, 10)): # make X >= Y 49 | X = target-Y 50 | if start == 0 and (X == 0 or Y == 0): # leading digit can't be 0 51 | continue 52 | yield X, Y 53 | 54 | if len(x)-start*2 <= 0: 55 | return left_carry == right_carry 56 | for new_left_carry in xrange(2): 57 | target = s[len(x)-1-start] + left_carry*10 - new_left_carry 58 | if s[start] != (target+right_carry)%10: 59 | continue 60 | new_right_carry = right_carry if len(x)-start*2 == 1 else (target+right_carry)//10 61 | for X, Y in gen_X_Y(): # it doesn't matter which of options we take except for making a leading 0 62 | set_digits(x, [X], start), set_digits(y, [Y], start) 63 | if find_pair_with_same_length(s, x, y, start+1, new_left_carry, new_right_carry): 64 | return True 65 | clear_digits(y, [Y], start), clear_digits(x, [X], start) 66 | break # if an option fails, other options also fail 67 | return False 68 | 69 | def find_pair_with_overhang_length(s, x, y, start, left_carry, right_carry, left_Y): 70 | def find_left_x(): 71 | left_X = to_int(s[len(x)-1-(start+overhang-1):len(x)-start][::-1]) + \ 72 | left_carry*(10**overhang) - new_left_carry - left_Y 73 | if not (0 <= left_X < 10**overhang): 74 | return None 75 | left_x = to_list(left_X) 76 | left_x = [0]*(overhang-len(left_x)) + left_x # 0-padding 77 | if start == 0 and left_x[0] == 0: # leading digit can't be 0 78 | return None 79 | if not set_digits(x, left_x, start): 80 | clear_digits(x, left_x, start) 81 | return None 82 | return left_x 83 | 84 | def find_left_y(): 85 | if len(y)-start*2 <= 0: 86 | return [], right_carry # pass current right carry if y is not updated 87 | right_y_len = min(len(y)-start*2, overhang) 88 | right_S, right_X = map(to_int, [s[start:start+right_y_len][::-1], left_x[:right_y_len][::-1]]) 89 | new_right_carry, right_Y = map(abs, divmod(right_S-right_X-right_carry, 10**right_y_len)) 90 | right_y = to_list(right_Y) 91 | right_y = [0]*(right_y_len-len(right_y)) + right_y # 0-padding 92 | left_y = right_y[::-1] 93 | if start == 0 and left_y[0] == 0: # leading digit can't be 0 94 | clear_digits(x, left_x, start) 95 | return None, None 96 | if not set_digits(y, left_y, start): 97 | clear_digits(y, left_y, start), clear_digits(x, left_x, start) 98 | return None, None 99 | return left_y, new_right_carry 100 | 101 | if len(x)-start*2 <= 0: 102 | return left_carry == right_carry 103 | overhang = min(len(x)-2*start, len(x)-len(y)) 104 | for new_left_carry in xrange(2): 105 | left_x = find_left_x() 106 | if left_x is None: 107 | continue 108 | left_y, new_right_carry = find_left_y() 109 | if left_y is None or new_right_carry is None: 110 | continue 111 | new_left_Y = 0 if len(y)-start*2 <= overhang else to_int(left_y[:(len(y)-start*2)-overhang]) 112 | if find_pair_with_overhang_length(s, x, y, start+overhang, 113 | new_left_carry, new_right_carry, new_left_Y): 114 | return True 115 | clear_digits(y, left_y, start), clear_digits(x, left_x, start) 116 | return False 117 | 118 | def find_pair(s, i, j, left_carry): 119 | x, y = [None]*i, [None]*j 120 | result = find_pair_with_same_length(s, x, y, 0, left_carry, 0) if i == j else \ 121 | find_pair_with_overhang_length(s, x, y, 0, left_carry, 0, 0) 122 | if not result: 123 | return None, None 124 | x.reverse(), y.reverse() 125 | return to_int(x), to_int(y) 126 | 127 | def wont_sum_must_now(): 128 | S = input() 129 | 130 | s = to_list(S) 131 | if s == s[::-1]: 132 | return S 133 | for P in gen_palindromes(S): 134 | s = to_list(S-P) 135 | s.reverse() 136 | carry = int(s[-1] == 1) 137 | for i in reversed(xrange(len(s)-carry, len(s)+1)): # prefer larger X 138 | left_carry = len(s)-i 139 | for j in xrange(1, i+1): 140 | X, Y = find_pair(s, i, j, left_carry) 141 | if X is None or Y is None: 142 | continue 143 | assert(X >= Y >= P) 144 | result = [X, Y] 145 | if P != 0: 146 | result.append(P) 147 | return " ".join(map(str, result)) 148 | assert(False) 149 | 150 | for case in xrange(input()): 151 | print 'Case #%d: %s' % (case+1, wont_sum_must_now()) 152 | --------------------------------------------------------------------------------