├── Round 1A ├── append_sort.py ├── append_sort2.py ├── prime_time.py └── hacked_exam.py ├── Round 1C ├── closest_pick.py ├── double_or_noting.py ├── roaring_years.py ├── double_or_noting2.py └── double_or_noting3.py ├── Qualification Round ├── cheating_detection.test.py ├── reversort_engineering.py ├── reversort.py ├── reversort_engineering2.short.py ├── moons_and_umbrellas.py ├── cheating_detection4.py ├── cheating_detection.py ├── reversort_engineering2.py ├── cheating_detection3.py ├── median_sort.py └── cheating_detection2.py ├── Round 2 ├── matrygons.py ├── minimum_sort.py ├── hidden_pancakes2.py ├── hidden_pancakes.py └── retiling.py ├── LICENSE ├── Round 1B ├── subtransmutation2.py ├── subtransmutation.py ├── broken_clock.py └── digit_blocks.py ├── Virtual World Finals ├── slide_circuits3.py ├── slide_circuits.py ├── slide_circuits2.py ├── ropes.py ├── cutting_cake.py ├── divisible_divisions.py ├── divisible_divisions2.py ├── infinitree_concise.py3 ├── infinitree_concise.py └── infinitree.py ├── Round 3 ├── build_a_pair3.py ├── build_a_pair.py ├── build_a_pair2.py ├── square_free.py ├── fence_design.py ├── binary_search_game.py ├── binary_search_game2.py └── binary_search_game3.py └── README.md /Round 1A/append_sort.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Round 1A - Problem A. Append Sort 4 | # https://codingcompetitions.withgoogle.com/codejam/round/000000000043585d/00000000007549e5 5 | # 6 | # Time: O(N * log(MAX_X) * size(int)) = O(N * log(MAX_X)) 7 | # Space: O(size(int)) = O(1) 8 | # 9 | 10 | def append_sort(): 11 | N = input() 12 | X = map(int, raw_input().strip().split()) 13 | 14 | result = 0 15 | for i in xrange(1, len(X)): 16 | cnt = 0 17 | while X[i] <= X[i-1]: 18 | X[i] *= 10 19 | cnt += 1 20 | if cnt > 1 and (X[i]//10) + (10**(cnt-1)-1) > X[i-1]: 21 | X[i] = X[i-1]+1 22 | cnt -= 1 23 | result += cnt 24 | return result 25 | 26 | for case in xrange(input()): 27 | print 'Case #%d: %s' % (case+1, append_sort()) 28 | -------------------------------------------------------------------------------- /Round 1C/closest_pick.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Round 1C - Problem A. Closest Pick 4 | # https://codingcompetitions.withgoogle.com/codejam/round/00000000004362d7/00000000007c0f00 5 | # 6 | # Time: O(NlogN) 7 | # Space: O(N) 8 | # 9 | 10 | def closest_pick(): 11 | N, K = map(int, raw_input().strip().split()) 12 | P = sorted(set(map(int, raw_input().strip().split()))) 13 | 14 | result = prev_max = P[0]-1 # one or two in the first interval 15 | for i in xrange(1, len(P)): 16 | result = max(result, prev_max+(P[i]-P[i-1])//2, P[i]-P[i-1]-1) # one or two in this interval 17 | prev_max = max(prev_max, (P[i]-P[i-1])//2) 18 | return float(max(result, prev_max+(K-P[-1])))/K # one or two in the last interval 19 | 20 | for case in xrange(input()): 21 | print 'Case #%d: %s' % (case+1, closest_pick()) 22 | -------------------------------------------------------------------------------- /Qualification Round/cheating_detection.test.py: -------------------------------------------------------------------------------- 1 | # Usage: pypy cheating_detection.test.py >data.in 2>data.out 2 | # pypy cheating_detection.py < data.in > data.tmp && diff -y --suppress-common-lines data.tmp data.out | wc -l 3 | 4 | from random import uniform, randint, seed 5 | from math import exp 6 | from sys import stderr 7 | 8 | def f(x): 9 | return 1.0/(1.0+exp(-x)) 10 | 11 | seed(0) 12 | T, P, S, Q = 1000, 86, 100, 10000 13 | print T 14 | print Q 15 | for case in xrange(T): 16 | s = [uniform(-3.0, 3.0) for _ in xrange(S)] 17 | q = [uniform(-3.0, 3.0) for _ in xrange(Q)] 18 | cheater = randint(0, S-1) 19 | print >>stderr, 'Case #%d: %s' % (case+1, cheater+1) 20 | for i in xrange(S): 21 | result = ['0']*Q 22 | for j in xrange(Q): 23 | if i == cheater and uniform(0.0, 1.0) <= 0.5: 24 | result[j] = '1' 25 | elif uniform(0.0, 1.0) <= f(s[i]-q[j]): 26 | result[j] = '1' 27 | print "".join(result) 28 | -------------------------------------------------------------------------------- /Qualification Round/reversort_engineering.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Qualification Round - Problem C. Reversort Engineering 4 | # https://codingcompetitions.withgoogle.com/codejam/round/000000000043580a/00000000006d12d7 5 | # 6 | # Time: O(N^2) 7 | # Space: O(1) 8 | # 9 | 10 | def reverse(L, i, j): 11 | while i < j: 12 | L[i], L[j] = L[j], L[i] 13 | i += 1 14 | j -= 1 15 | 16 | def reversort_engineering(): 17 | N, C = map(int, raw_input().strip().split()) 18 | 19 | if not (N-1 <= C <= (N+2)*(N-1)//2): 20 | return "IMPOSSIBLE" 21 | result = range(1, N+1) 22 | for i in reversed(xrange(N-1)): 23 | l = min(C-i, N-i) # greedy 24 | C -= l 25 | # result[i:i+l] = result[i:i+l][::-1] # Space: O(N) 26 | reverse(result, i, i+l-1) # Space: O(1) 27 | return " ".join(map(str, result)) 28 | 29 | for case in xrange(input()): 30 | print 'Case #%d: %s' % (case+1, reversort_engineering()) 31 | -------------------------------------------------------------------------------- /Round 2/matrygons.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Round 2 - Problem B. Matrygons 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000435915/00000000007dbf06 5 | # 6 | # Time: precompute: O(NlogN) 7 | # runtime: O(1) 8 | # Space: O(N) 9 | # 10 | 11 | def matrygons(): 12 | N = input() 13 | return dp[N] 14 | 15 | MAX_N = 10**6 16 | pseudo = [0]*(MAX_N+1) 17 | dp = [0]*(MAX_N+1) 18 | for i in xrange(1, MAX_N//2+1): # time < sum(N/i for i in xrange(1, N)) = O(NlogN) 19 | for j in xrange(2*i, MAX_N+1, i): # min size of pseudo-polygon could be at least 2 20 | pseudo[j] = max(pseudo[j], pseudo[i-1]+1) # pseudo(j) = max(pseudo(i-1)+1 for each i s.t. j%i == 0 and j//i >= 2) 21 | if j//i >= 3: # min size of polygon should be at least 3 22 | dp[j] = max(dp[j], pseudo[i-1]+1) # dp(j) = max(pseudo(i-1)+1 for each i s.t. j%i == 0 and j//i >= 3) 23 | for case in xrange(input()): 24 | print 'Case #%d: %s' % (case+1, matrygons()) 25 | -------------------------------------------------------------------------------- /Qualification Round/reversort.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Qualification Round - Problem A. Reversort 4 | # https://codingcompetitions.withgoogle.com/codejam/round/000000000043580a/00000000006d0a5c 5 | # 6 | # Time: O(N^2) 7 | # Space: O(1) 8 | # 9 | 10 | def min_idx(L, i): 11 | m = i 12 | for j in xrange(i, len(L)): 13 | if L[j] < L[m]: 14 | m = j 15 | return m 16 | 17 | def reverse(L, i, j): 18 | while i < j: 19 | L[i], L[j] = L[j], L[i] 20 | i += 1 21 | j -= 1 22 | 23 | def reversort(): 24 | N = input() 25 | L = map(int, raw_input().strip().split()) 26 | 27 | result = 0 28 | for i in xrange(len(L)-1): 29 | # m = L.index(min(L[i:])) # Space: O(N) 30 | m = min_idx(L, i) # Space: O(1) 31 | # L[i:m+1] = L[i:m+1][::-1] # Space: O(N) 32 | reverse(L, i, m) # Space: O(1) 33 | result += m-i+1 34 | return result 35 | 36 | for case in xrange(input()): 37 | print 'Case #%d: %s' % (case+1, reversort()) 38 | -------------------------------------------------------------------------------- /Round 2/minimum_sort.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Round 2 - Problem A. Minimum Sort 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000435915/00000000007dc51c 5 | # 6 | # Time: O(ClogN) = C/N + C/(N-1) + ... + C/2 = 4.187 * 10^8 (given N = 100, C = 10^8) 7 | # Space: O(1) 8 | # 9 | # Usage: python interactive_runner.py python3 testing_tool.py -- python minimum_sort.py 10 | # 11 | 12 | from sys import stdout 13 | 14 | def query(i, j): 15 | print "M %s %s" % (i, j) 16 | stdout.flush() 17 | return input() 18 | 19 | def swap(i, j): 20 | print "S %s %s" % (i, j) 21 | stdout.flush() 22 | return input() 23 | 24 | def done(): 25 | print "D" 26 | stdout.flush() 27 | return input() 28 | 29 | def minimum_sort(): 30 | for i in xrange(1, N): 31 | idx = query(i, N) 32 | if idx != i: 33 | swap(i, idx) 34 | if done() != 1: 35 | exit() 36 | 37 | T, N = map(int, raw_input().strip().split()) 38 | for case in xrange(T): 39 | minimum_sort() 40 | -------------------------------------------------------------------------------- /Qualification Round/reversort_engineering2.short.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Qualification Round - Problem C. Reversort Engineering 4 | # https://codingcompetitions.withgoogle.com/codejam/round/000000000043580a/00000000006d12d7 5 | # 6 | # Time: O(N) 7 | # Space: O(N), shorter, but with extra space O(N) 8 | # 9 | 10 | def reversort_engineering(): 11 | N, C = map(int, raw_input().strip().split()) 12 | 13 | if not (N-1 <= C <= (N+2)*(N-1)//2): 14 | return "IMPOSSIBLE" 15 | result = [0]*N 16 | for i in xrange(N): 17 | l = min(C-(N-1-i)+1, N-i) # greedy 18 | C -= l 19 | fill = (list(reversed(range(i+1, i+1+l))) + range(i+1+l, N+1)) if l != N-i else [i+1] 20 | if i%2 == 0: 21 | result[N-1-i//2+1-len(fill):N-1-i//2+1] = fill 22 | else: 23 | result[i//2:i//2+len(fill)] = fill[::-1] 24 | if l != N-i: 25 | break 26 | return " ".join(map(str, result)) 27 | 28 | for case in xrange(input()): 29 | print 'Case #%d: %s' % (case+1, reversort_engineering()) 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 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/moons_and_umbrellas.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Qualification Round - Problem B. Moons and Umbrellas 4 | # https://codingcompetitions.withgoogle.com/codejam/round/000000000043580a/00000000006d1145 5 | # 6 | # Time: O(N) 7 | # Space: O(1) 8 | # 9 | 10 | def moons_and_umbrellas(): 11 | X, Y, S = raw_input().strip().split() 12 | X, Y = int(X), int(Y) 13 | dp = {} 14 | prev = None 15 | for c in S: 16 | new_dp = {} 17 | for i, j, cost in [('C', 'J', Y), ('J', 'C', X)]: 18 | if c == j: 19 | new_dp[i] = INF 20 | elif prev is None: 21 | new_dp[i] = 0 22 | elif prev == i: 23 | new_dp[i] = dp[i] 24 | elif prev == j: 25 | new_dp[i] = dp[j]+cost 26 | elif prev == '?': 27 | new_dp[i] = min(dp[i], dp[j]+cost) 28 | dp = new_dp 29 | prev = c 30 | return min(dp.itervalues()) 31 | 32 | INF = float("inf") 33 | for case in xrange(input()): 34 | print 'Case #%d: %s' % (case+1, moons_and_umbrellas()) 35 | -------------------------------------------------------------------------------- /Round 1A/append_sort2.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Round 1A - Problem A. Append Sort 4 | # https://codingcompetitions.withgoogle.com/codejam/round/000000000043585d/00000000007549e5 5 | # 6 | # Time: O(N * log(MAX_X)) 7 | # Space: O(log(MAX_X)) 8 | # 9 | 10 | def append_sort(): 11 | N = input() 12 | X = map(list, raw_input().strip().split()) 13 | 14 | result = 0 15 | curr = list(X[0]) 16 | for i in xrange(1, len(X)): 17 | prev, curr = curr, list(X[i]) 18 | if len(curr) > len(prev): 19 | continue 20 | result += (len(prev)-len(curr)) 21 | if curr[:len(curr)] < prev[:len(curr)]: 22 | curr.extend(['0']*((len(prev)-len(curr))+1)) 23 | result += 1 24 | elif curr[:len(curr)] > prev[:len(curr)]: 25 | curr.extend(['0']*(len(prev)-len(curr))) 26 | else: 27 | if len(prev)-len(curr) != 0 and int("".join(prev[len(curr):]))+1 < 10**(len(prev)-len(curr)): 28 | curr.extend(list(str(int("".join(prev[len(curr):]))+1).zfill((len(prev)-len(curr))))) 29 | else: 30 | curr.extend(['0']*((len(prev)-len(curr))+1)) 31 | result += 1 32 | return result 33 | 34 | for case in xrange(input()): 35 | print 'Case #%d: %s' % (case+1, append_sort()) 36 | -------------------------------------------------------------------------------- /Qualification Round/cheating_detection4.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Qualification Round - Problem E. Cheating Detection 4 | # https://codingcompetitions.withgoogle.com/codejam/round/000000000043580a/00000000006d1155 5 | # 6 | # Time: O(S * Q) 7 | # Space: O(S + Q) 8 | # 9 | # Correlation coefficient method 10 | # Accuracy: 895/1000 = 89.5% 11 | # 12 | 13 | def normalized(a): 14 | avg = float(sum(a))/len(a) 15 | for i in xrange(len(a)): 16 | a[i] -= avg 17 | 18 | def covariance(a, b): 19 | return sum(a[i]*b[i] for i in xrange(len(a))) 20 | 21 | def correlation(a, b): 22 | return covariance(a, b) / covariance(a, a)**0.5 / covariance(b, b)**0.5 23 | 24 | def cheating_detection(): 25 | scores = [] 26 | q_count = [0]*Q 27 | for i in xrange(S): 28 | scores.append(map(int, list(raw_input().strip()))) 29 | for j, c in enumerate(scores[i]): 30 | q_count[j] += c 31 | normalized(q_count) 32 | result, min_corr = 0, 1.0 33 | for i, score in enumerate(scores): 34 | normalized(score) 35 | corr = correlation(score, q_count) 36 | if corr < min_corr: 37 | min_corr = corr 38 | result = i 39 | return result+1 40 | 41 | S, Q, T, P = 100, 10000, input(), input() 42 | for case in xrange(T): 43 | print 'Case #%d: %s' % (case+1, cheating_detection()) 44 | -------------------------------------------------------------------------------- /Round 1B/subtransmutation2.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Round 1B - Problem B. Subtransmutation 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000435baf/00000000007ae4aa 5 | # 6 | # Time: O(MAX_M^2), MAX_M is the max possible m in the given limit 7 | # Space: O(MAX_M) 8 | # 9 | 10 | from fractions import gcd 11 | 12 | def get_U(U, i): 13 | return U[i] if i < len(U) else 0 14 | 15 | def check(A, B, U, x): 16 | count = [0]*x 17 | count[-1] = 1 18 | for i in reversed(xrange(len(count))): 19 | if count[i] < get_U(U, i): 20 | return False 21 | extra = count[i]-get_U(U, i) 22 | if A <= i: 23 | count[i-A] += extra 24 | if B <= i: 25 | count[i-B] += extra 26 | return True 27 | 28 | def subtransmutation(): 29 | N, A, B = map(int, raw_input().strip().split()) 30 | U = map(int, raw_input().strip().split()) 31 | g = gcd(A, B) 32 | k = None 33 | for i, c in enumerate(U, 1): 34 | if not c: 35 | continue 36 | if k is None: 37 | k = i%g 38 | continue 39 | if i%g != k: 40 | return "IMPOSSIBLE" 41 | result = (N+1)+(k-(N+1))%g 42 | while not check(A, B, U, result): 43 | result += g 44 | return result 45 | 46 | for case in xrange(input()): 47 | print 'Case #%d: %s' % (case+1, subtransmutation()) 48 | -------------------------------------------------------------------------------- /Qualification Round/cheating_detection.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Qualification Round - Problem E. Cheating Detection 4 | # https://codingcompetitions.withgoogle.com/codejam/round/000000000043580a/00000000006d1155 5 | # 6 | # Time: O(S * Q + QlogQ) 7 | # Space: O(S + Q) 8 | # 9 | # Inversion count method 10 | # Accuracy: 996/1000 = 99.6% 11 | # 12 | 13 | def cheating_detection(): 14 | scores = [] 15 | q_count = [0]*Q 16 | for i in xrange(S): 17 | scores.append(map(int, list(raw_input().strip()))) 18 | for j, c in enumerate(scores[i]): 19 | q_count[j] += c 20 | questions = sorted(range(Q), key=lambda x:q_count[x]) 21 | result, max_score = 0, 0.0 22 | for i in xrange(S): 23 | cnt = [0]*2 24 | inv = 0 25 | for j in questions: 26 | if not scores[i][j]: 27 | cnt[0] += 1 28 | inv += cnt[1] 29 | else: 30 | cnt[1] += 1 31 | score = float(inv)/(1+cnt[0])/(1+cnt[1]) # normalize inversions by weakness and strength, both ends are divided less and the middle are divided more 32 | if score > max_score: # the higher score is, the more uniform distribution of corrects is 33 | max_score = score 34 | result = i 35 | return result+1 36 | 37 | S, Q, T, P = 100, 10000, input(), input() 38 | for case in xrange(T): 39 | print 'Case #%d: %s' % (case+1, cheating_detection()) 40 | -------------------------------------------------------------------------------- /Qualification Round/reversort_engineering2.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Qualification Round - Problem C. Reversort Engineering 4 | # https://codingcompetitions.withgoogle.com/codejam/round/000000000043580a/00000000006d12d7 5 | # 6 | # Time: O(N) 7 | # Space: O(1) 8 | # 9 | 10 | def reverse(L, i, j): 11 | while i < j: 12 | L[i], L[j] = L[j], L[i] 13 | i += 1 14 | j -= 1 15 | 16 | def reversort_engineering(): 17 | N, C = map(int, raw_input().strip().split()) 18 | 19 | if not (N-1 <= C <= (N+2)*(N-1)//2): 20 | return "IMPOSSIBLE" 21 | result = [0]*N 22 | for i in xrange(N): 23 | l = min(C-(N-1-i)+1, N-i) # greedy 24 | C -= l 25 | if l != N-i: 26 | break 27 | if i%2 == 0: 28 | result[N-1-i//2] = i+1 29 | else: 30 | result[i//2] = i+1 31 | if i%2 == 0: 32 | k = i+1 33 | for j in xrange((N-1-i//2+1)-(N-i), N-1-i//2+1): 34 | result[j] = k 35 | k += 1 36 | reverse(result, (N-1-i//2+1)-(N-i), ((N-1-i//2+1)-(N-i))+l-1) # Space: O(1) 37 | else: 38 | k = i+1 39 | for j in reversed(xrange(i//2, i//2+(N-i))): 40 | result[j] = k 41 | k += 1 42 | reverse(result, (i//2+(N-i)-1)-l+1, i//2+(N-i)-1) # Space: O(1) 43 | return " ".join(map(str, result)) 44 | 45 | for case in xrange(input()): 46 | print 'Case #%d: %s' % (case+1, reversort_engineering()) 47 | -------------------------------------------------------------------------------- /Round 1B/subtransmutation.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Round 1B - Problem B. Subtransmutation 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000435baf/00000000007ae4aa 5 | # 6 | # Time: O(MAX_M^2), MAX_M is the max possible m in the given limit 7 | # Space: O(MAX_M) 8 | # 9 | 10 | def get_U(U, i): 11 | return U[i] if i < len(U) else 0 12 | 13 | def check(A, B, U, x): 14 | count = [0]*x 15 | count[-1] = 1 16 | for i in reversed(xrange(len(count))): 17 | if count[i] < get_U(U, i): 18 | return False 19 | extra = count[i]-get_U(U, i) 20 | if A <= i: 21 | count[i-A] += extra 22 | if B <= i: 23 | count[i-B] += extra 24 | return True 25 | 26 | def subtransmutation(): 27 | N, A, B = map(int, raw_input().strip().split()) 28 | U = map(int, raw_input().strip().split()) 29 | result = N+1 30 | while result <= MAX_M: 31 | if check(A, B, U, result): 32 | return result 33 | result += 1 34 | return "IMPOSSIBLE" 35 | 36 | ''' 37 | GUESS = 1000 38 | MAX_N = MAX_U = 20 39 | MAX_A = MAX_B = 20 40 | U = [MAX_U]*MAX_N 41 | MAX_M = 0 42 | for A in xrange(1, MAX_A+1): 43 | for B in xrange(A+1, MAX_B+1): 44 | result = MAX_N+1 45 | while result <= GUESS: 46 | if check(A, B, U, result): 47 | MAX_M = max(MAX_M, result) 48 | break 49 | result += 1 50 | assert(MAX_M == 402) 51 | ''' 52 | MAX_M = 402 53 | for case in xrange(input()): 54 | print 'Case #%d: %s' % (case+1, subtransmutation()) 55 | -------------------------------------------------------------------------------- /Qualification Round/cheating_detection3.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Qualification Round - Problem E. Cheating Detection 4 | # https://codingcompetitions.withgoogle.com/codejam/round/000000000043580a/00000000006d1155 5 | # 6 | # Time: O(S * Q + SlogS + QlogQ) 7 | # Space: O(S + Q) 8 | # 9 | # Difference with expected distribution method 10 | # Accuracy: 1000/1000 = 100.0% 11 | # 12 | 13 | from math import exp 14 | 15 | def f(x): 16 | return 1.0/(1.0+exp(-x)) 17 | 18 | def cheating_detection(): 19 | scores = [] 20 | p_count = [0]*S 21 | q_count = [0]*Q 22 | for i in xrange(S): 23 | scores.append(map(int, list(raw_input().strip()))) 24 | for j, c in enumerate(scores[i]): 25 | p_count[i] += c 26 | q_count[j] += c 27 | players = sorted(range(S), key=lambda x:p_count[x]) 28 | questions = sorted(range(Q), key=lambda x:q_count[x]) 29 | si = MIN 30 | result, max_score, si = 0, 0.0, MIN 31 | for i in players: 32 | score, qj = 0.0, MAX 33 | for j in questions: 34 | score += (scores[i][j]-f(si-qj))**2 35 | qj -= Q_D 36 | score /= (1+p_count[i])*(1+(Q-p_count[i])) # normalize score by weakness and strength, both ends are divided less and the middle are divided more 37 | if score > max_score: 38 | max_score = score 39 | result = i 40 | si += S_D 41 | return result+1 42 | 43 | MIN, MAX = -3.0, 3.0 44 | S, Q, = 100, 10000 45 | S_D, Q_D = (MAX-MIN)/S, (MAX-MIN)/Q 46 | T, P = input(), input() 47 | for case in xrange(T): 48 | print 'Case #%d: %s' % (case+1, cheating_detection()) 49 | -------------------------------------------------------------------------------- /Qualification Round/median_sort.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Qualification Round - Problem D. Median Sort 4 | # https://codingcompetitions.withgoogle.com/codejam/round/000000000043580a/00000000006d1284 5 | # 6 | # Time: O(N^2) 7 | # Space: O(1) 8 | # Usage: python interactive_runner.py python3 testing_tool.py 2 -- python median_sort.py 9 | # 10 | 11 | from sys import stdout 12 | 13 | def query(i, j, k): 14 | print i, j, k 15 | stdout.flush() 16 | return input() 17 | 18 | def check(result): 19 | print " ".join(map(str, result)) 20 | stdout.flush() 21 | ok = raw_input().strip() 22 | if ok != "1": # error 23 | exit() 24 | 25 | def median_sort(): 26 | result = [1, 2] 27 | for i in xrange(3, N+1): # Time: O(N) 28 | left, right = 0, len(result)-1 29 | while right-left >= 1: # Time: O(logN) 30 | m1 = left + (right-left)//3 31 | m2 = right - (right-left)//3 32 | x = query(result[m1], result[m2], i) 33 | if x == result[m1]: 34 | right = m1-1 35 | if left == right: # padded for the last query 36 | right += 1 37 | elif x == result[m2]: 38 | left = m2+1 39 | if left == right: # padded for the last query 40 | left -= 1 41 | else: 42 | left, right = m1+1, m2-1 43 | if left == right: # padded for the last query 44 | left -= 1 45 | result.insert(left, i) # Time: O(N) 46 | check(result) 47 | 48 | T, N, Q = map(int, raw_input().strip().split()) 49 | for case in xrange(T): 50 | median_sort() 51 | -------------------------------------------------------------------------------- /Round 1C/double_or_noting.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Round 1C - Problem C. Double or NOTing 4 | # https://codingcompetitions.withgoogle.com/codejam/round/00000000004362d7/00000000007c1139 5 | # 6 | # Time: O(K * (|E| + |S|)), K is the number of bit groups of S 7 | # Space: O(|E| + |S|) 8 | # 9 | # shorter but slower solution 10 | # 11 | 12 | # from re import match 13 | 14 | def flip(s): 15 | return "".join(["01"[c == '0'] for c in s]).lstrip('0') or "0" 16 | 17 | def not_count(s): 18 | s += '0' # if s ends with '1', it requires one more "not" operation, which could be easily counted by appending a '0' 19 | return sum(int(s[i] != s[i+1]) for i in reversed(xrange(len(s)-1))) 20 | 21 | def find_prefix_and_count(S, E): 22 | result = float("inf") 23 | X = 0 24 | while S != "0": 25 | if S == E[:len(S)] and X >= not_count(E[len(S):]): 26 | return X+(len(E)-len(S)), None 27 | S = flip(S) 28 | X += 1 29 | return result, X 30 | 31 | def double_or_noting(): 32 | S, E = raw_input().strip().split() 33 | 34 | result, X = find_prefix_and_count(S, E) 35 | if result != float("inf"): 36 | return result 37 | if X >= not_count(E): 38 | return X+len(E)-(E[0] == '0') 39 | cnt = not_count(E[1:]) 40 | if cnt == 0: 41 | # assert(match("^10*$", E)) 42 | return X+1+(len(E)-1) # S =X=> "0" =1=> "1" =(len(E)-1)=> "10*" 43 | if cnt == 1: 44 | # assert(match("^11+0*$", E)) 45 | return X+1+len(E)+1 # S =X=> "0" =1=> "1" =k=> "100+" =1=> "11+" =(len(E)-k)=> "11+0*", where 2 <= k <= len(E) 46 | return "IMPOSSIBLE" 47 | 48 | for case in xrange(input()): 49 | print 'Case #%d: %s' % (case+1, double_or_noting()) 50 | -------------------------------------------------------------------------------- /Round 1B/broken_clock.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Round 1B - Problem A. Broken Clock 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000435baf/00000000007ae694 5 | # 6 | # Time: O(1) 7 | # Space: O(1) 8 | # 9 | 10 | from itertools import permutations 11 | 12 | def linear_congruence(a, m, b): # Time: O(logN), the same as gcd, Space: O(logN) 13 | # gcd(a, m) = g and g|b, find x, s.t. ax % m = b % m 14 | # => (a%m)x = my+(b%m) 15 | # => gcd(m, a%m) = g and g|-(b%m), find y, s.t. my % (a%m) = -(b%m) % (a%m) 16 | # => y = linear_congruence(m, a%m, -(b%m)) 17 | # => x = (my+(b%m))/(a%m) 18 | ambs = [] 19 | while m: 20 | a, m, b = m, a%m, -(b%m) 21 | if m: 22 | ambs.append((m, a, -b)) 23 | x = a # a is gcd 24 | while ambs: 25 | a, m, b = ambs.pop() 26 | x = (m*x+b)//a 27 | return x 28 | 29 | def format_ticks(t): 30 | s, n = divmod(t, TICKS_PER_SECOND) 31 | m, s = divmod(s, 60) 32 | h, m = divmod(m, 60) 33 | return "%s %s %s %s" % (h, m, s, n) 34 | 35 | def solution(): 36 | A, B, C = map(int, raw_input().strip().split()) 37 | # t%TOTAL = (h+x)%TOTAL 38 | # 12*t%TOTAL = (m+x)%TOTAL 39 | # 720*t%TOTAL = (s+x)%TOTAL 40 | # => find t s.t. 41 | # 11*t%TOTAL = (m-h)%TOTAL 42 | # 708*t%TOTAL = (s-m)%TOTAL 43 | for h, m, s in set(permutations([A, B, C])): 44 | t = INV_11*(m-h)%TOTAL 45 | if 708*t%TOTAL == (s-m)%TOTAL: 46 | break 47 | return format_ticks(t) 48 | 49 | TICKS_PER_SECOND = 10**9 50 | TOTAL = 12*60*60*TICKS_PER_SECOND 51 | INV_11 = linear_congruence(11, TOTAL, 1)%TOTAL # Time: O(log(min(11, TOTAL))) = O(log11) = O(1) 52 | for case in xrange(input()): 53 | print 'Case #%d: %s' % (case+1, solution()) 54 | -------------------------------------------------------------------------------- /Round 1C/roaring_years.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Round 1C - Problem B. Roaring Years 4 | # https://codingcompetitions.withgoogle.com/codejam/round/00000000004362d7/00000000007c0f01 5 | # 6 | # Time: O(D^2 * logD), D is the digit count of Y, since sum((D/n)*D for n in [2, D+1]) = D^2 * O(logD) 7 | # Space: O(D) 8 | # 9 | 10 | def ceil(x, n): 11 | return (x-1)//n+1 12 | 13 | def floor(x, n): 14 | return x//n 15 | 16 | def f(x, n): 17 | return int("".join(str(i) for i in xrange(x, x+n))) 18 | 19 | def binary_search(left, right, check): 20 | while left <= right: 21 | mid = left + (right-left)//2 22 | if check(mid): 23 | right = mid-1 24 | else: 25 | left = mid+1 26 | return left 27 | 28 | def min_fn(Y, n): 29 | y = int(Y) 30 | # find any x, s.t. f(x, n) > y 31 | # => X = str(x), f(x, n) > X*n >= 10**len(Y) > y 32 | # => len(X)*n >= len(Y)+1 33 | # => len(X) = ceil(len(Y)+1, n) 34 | # => x is in the range [10**(ceil(len(Y)+1, n)-1), 10**ceil(len(Y)+1, n)-1] 35 | # => let right be 10**(ceil(len(Y)+1, n)-1) 36 | right = 10**(ceil(len(Y)+1, n)-1) 37 | assert(f(right, n) > y) 38 | # find any x, s.t. f(x, n) <= y 39 | # => X = str(x), f(x, n) < X*n <= 10**(len(Y)-1) <= y 40 | # => len(X)*n <= len(Y) 41 | # => len(X) = floor(len(Y), n) 42 | # => x is in the range [1, 10**(floor(len(Y), n)-1)-n+1] 43 | # => let left be max(1, 10**(floor(len(Y), n)-1)-n+1) 44 | left = max(1, 10**(floor(len(Y), n)-1)-n+1) 45 | x = binary_search(left, right, lambda x: f(x, n) > y) # find the smallest x, s.t. f(x, n) > y 46 | return f(x, n) 47 | 48 | def roaring_years(): 49 | Y = raw_input().strip() 50 | return min(min_fn(Y, n) for n in xrange(2, (len(Y)+1)+1)) 51 | 52 | for case in xrange(input()): 53 | print 'Case #%d: %s' % (case+1, roaring_years()) 54 | -------------------------------------------------------------------------------- /Qualification Round/cheating_detection2.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Qualification Round - Problem E. Cheating Detection 4 | # https://codingcompetitions.withgoogle.com/codejam/round/000000000043580a/00000000006d1155 5 | # 6 | # Time: O(S * Q + SlogS + QlogQ) 7 | # Space: O(S + Q) 8 | # 9 | # Difference with neighbors in easiest and hardest 5% questions method 10 | # Accuracy: 982/1000 = 98.2% 11 | # 12 | 13 | def diff(player1, player2, extreme_questions): 14 | return abs(sum(player1[j] for j in extreme_questions) - sum(player2[j] for j in extreme_questions)) 15 | 16 | def neighbor_diffs(scores, players, extreme_questions, i): 17 | diffs = cnt = 0 18 | if i-1 >= 0: 19 | diffs += diff(scores[players[i-1]], scores[players[i]], extreme_questions) 20 | cnt += 1 21 | if i+1 < S: 22 | diffs += diff(scores[players[i]], scores[players[i+1]], extreme_questions) 23 | cnt += 1 24 | return float(diffs)/cnt 25 | 26 | def cheating_detection(): 27 | scores = [] 28 | p_count = [0]*S 29 | q_count = [0]*Q 30 | for i in xrange(S): 31 | scores.append(map(int, list(raw_input().strip()))) 32 | for j, c in enumerate(scores[i]): 33 | p_count[i] += c 34 | q_count[j] += c 35 | players = sorted(range(S), key=lambda x:p_count[x]) 36 | questions = sorted(range(Q), key=lambda x:q_count[x]) 37 | extreme_questions = [questions[j] for j in xrange(int(Q*EXTREME_RATIO))] + [questions[j] for j in xrange(Q-int(Q*EXTREME_RATIO), Q)] 38 | result = 0 39 | for i in xrange(S): 40 | if neighbor_diffs(scores, players, extreme_questions, i) > neighbor_diffs(scores, players, extreme_questions, result): 41 | result = i 42 | return players[result]+1 43 | 44 | EXTREME_RATIO = 0.05 45 | S, Q, T, P = 100, 10000, input(), input() 46 | for case in xrange(T): 47 | print 'Case #%d: %s' % (case+1, cheating_detection()) 48 | -------------------------------------------------------------------------------- /Round 2/hidden_pancakes2.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Round 2 - Problem C. Hidden Pancakes 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000435915/00000000007dc20c 5 | # 6 | # Time: O(N) 7 | # Space: O(N) 8 | # 9 | 10 | def factorial(n): 11 | while len(fact) <= n: # lazy initialization 12 | fact.append(fact[-1]*len(fact) % MOD) 13 | return fact[n] 14 | 15 | def inverse(n): 16 | while len(inv) <= n: # lazy initialization 17 | inv.append(inv[MOD%len(inv)]*(MOD-MOD//len(inv)) % MOD) # https://cp-algorithms.com/algebra/module-inverse.html 18 | return inv[n] 19 | 20 | def hidden_pancakes(): 21 | N = input() 22 | V = map(int, raw_input().strip().split()) 23 | V.append(1) # add a virtual value to count the permutation of remaining subtrees 24 | result = factorial(N) # max number of permutations 25 | stk = [] # keep the size of each subtree satisfying v 26 | for v in V: 27 | if not (v <= len(stk)+1): # v minus the number of subtrees should be less than or equal to 1 28 | return 0 29 | cnt = 0 30 | while v < len(stk)+1: # pop subtree size and form a new tree until the number of subtrees on stack is v 31 | # a tree structure is constructed by v, count the valid permutations: 32 | # the largest pancake size of each subtee on stack keeps monotonically decreasing. 33 | # since the total size of the current merged tree is cnt, we can only choose the largest pancake as root from cnt pancakes. 34 | # thus we could inversely get the true number of valid permutations from max number of permutations. 35 | # so the number of valid permutations is as follows: 36 | cnt += stk.pop() 37 | result = (result * inverse(cnt)) % MOD 38 | stk.append(cnt+1) # len(stk) == v 39 | return result 40 | 41 | MOD = 10**9+7 42 | fact = [1, 1] 43 | inv = [0, 1] 44 | for case in xrange(input()): 45 | print 'Case #%d: %s' % (case+1, hidden_pancakes()) 46 | -------------------------------------------------------------------------------- /Round 2/hidden_pancakes.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Round 2 - Problem C. Hidden Pancakes 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000435915/00000000007dc20c 5 | # 6 | # Time: O(N) 7 | # Space: O(N) 8 | # 9 | 10 | def nCr(n, k): 11 | while len(inv) <= n: # lazy initialization 12 | fact.append(fact[-1]*len(inv) % MOD) 13 | inv.append(inv[MOD%len(inv)]*(MOD-MOD//len(inv)) % MOD) # https://cp-algorithms.com/algebra/module-inverse.html 14 | inv_fact.append(inv_fact[-1]*inv[-1] % MOD) 15 | return (fact[n]*inv_fact[n-k] % MOD) * inv_fact[k] % MOD 16 | 17 | def hidden_pancakes(): 18 | N = input() 19 | V = map(int, raw_input().strip().split()) 20 | V.append(1) # add a virtual value to count the permutation of remaining subtrees 21 | result = 1 22 | stk = [] # keep the size of each subtree satisfying v 23 | for v in V: 24 | if not (v <= len(stk)+1): # v minus the number of subtrees should be less than or equal to 1 25 | return 0 26 | cnt = 0 27 | while v < len(stk)+1: # pop subtree size and form a new tree until the number of subtrees on stack is v 28 | # a tree structure is constructed by v, count the valid permutations: 29 | # the largest pancake size of each subtee on stack keeps monotonically decreasing. 30 | # to merge the subtree on the top of stack, use its largest pancake as root of a new tree, 31 | # the rest part of the subtree is as a left subtree with size stk[-1]-1, 32 | # and the previously merged subtree is as a right subtree with size cnt. 33 | # so the number of valid permutations is as follows: 34 | result = result * nCr(cnt+(stk[-1]-1), (stk[-1]-1)) % MOD 35 | cnt += stk.pop() 36 | stk.append(cnt+1) # len(stk) == v 37 | return result 38 | 39 | MOD = 10**9+7 40 | fact = [1, 1] 41 | inv = [0, 1] 42 | inv_fact = [1, 1] 43 | for case in xrange(input()): 44 | print 'Case #%d: %s' % (case+1, hidden_pancakes()) -------------------------------------------------------------------------------- /Virtual World Finals/slide_circuits3.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Virtual World Finals - Problem B. Slide Circuits 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000436329/000000000084f7b2 5 | # 6 | # Time: O(B + N + SlogS) 7 | # Space: O(B + SlogS) 8 | # 9 | # polynomial hashes solution (slower) 10 | # 11 | 12 | def poly_hash_id_gen(B, basis): 13 | for _ in xrange(B): 14 | yield basis[0] 15 | basis[0] = (basis[0]*p)%MOD 16 | 17 | def get_sum(prefix, L, R, M): 18 | return sub(prefix[M][R//M], prefix[M][(L-1)//M]) 19 | 20 | def slide_circuits(): 21 | B, S, N = map(int, raw_input().strip().split()) 22 | basis = [1] 23 | in_ids = [i for i in poly_hash_id_gen(B, basis)] 24 | out_ids = [i for i in poly_hash_id_gen(B, basis)] 25 | total_hash = add(reduce(add, in_ids), reduce(add, out_ids)) 26 | slide_hashes = [0]*S 27 | lookup = {} 28 | for i in xrange(S): 29 | X, Y = map(int, raw_input().strip().split()) 30 | slide_hashes[i] = add(out_ids[X-1], in_ids[Y-1]) 31 | lookup[sub(total_hash, slide_hashes[i])] = i # 0-indexed 32 | prefix = [[0] for _ in xrange(S+1)] 33 | for m in xrange(1, S+1): # sum of harmonic series: O(S/1 + S/2 + ... + S/S) = O(S * (1 + 1/2 + ... + 1/S)) = O(SlogS) 34 | for i in xrange(m, S+1, m): 35 | prefix[m].append(add(prefix[m][-1], slide_hashes[i-1])) 36 | result = [0]*N 37 | curr_hash = 0 38 | for i in xrange(N): 39 | A, L, R, M = raw_input().strip().split() 40 | L, R, M = int(L), int(R), int(M) 41 | curr_hash = HASH_OP[A](curr_hash, get_sum(prefix, L, R, M)) 42 | if curr_hash in lookup: 43 | result[i] = lookup[curr_hash]+1 44 | return " ".join(map(lambda x: str(x) if x else "X", result)) 45 | 46 | MOD, p = 2**64-59, 113 # MOD is max 64-bit prime 47 | add = lambda a, b: (a+b)%MOD 48 | sub = lambda a, b: (a-b)%MOD 49 | HASH_OP = {'E':add, 'D':sub} 50 | for case in xrange(input()): 51 | print 'Case #%d: %s' % (case+1, slide_circuits()) 52 | -------------------------------------------------------------------------------- /Virtual World Finals/slide_circuits.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Virtual World Finals - Problem B. Slide Circuits 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000436329/000000000084f7b2 5 | # 6 | # Time: O(B + N + SlogS) 7 | # Space: O(B + SlogS) 8 | # 9 | # sum of random values solution 10 | # 11 | 12 | from random import seed, randint 13 | from operator import add, sub 14 | 15 | def random_id_gen(B, ids_set): 16 | MAX_RAND_ID = MAX_UINT64//(B*2) 17 | for _ in xrange(B): 18 | x = randint(1, MAX_RAND_ID) 19 | while True: 20 | x = randint(1, MAX_RAND_ID) 21 | if x not in ids_set: 22 | break 23 | ids_set.add(x) 24 | yield x 25 | 26 | def get_sum(prefix, L, R, M): 27 | return sub(prefix[M][R//M], prefix[M][(L-1)//M]) 28 | 29 | def slide_circuits(): 30 | B, S, N = map(int, raw_input().strip().split()) 31 | ids_set = set() 32 | in_ids = [i for i in random_id_gen(B, ids_set)] 33 | out_ids = [i for i in random_id_gen(B, ids_set)] 34 | total_hash = add(reduce(add, in_ids), reduce(add, out_ids)) 35 | slide_hashes = [0]*S 36 | lookup = {} 37 | for i in xrange(S): 38 | X, Y = map(int, raw_input().strip().split()) 39 | slide_hashes[i] = add(out_ids[X-1], in_ids[Y-1]) 40 | lookup[sub(total_hash, slide_hashes[i])] = i # 0-indexed 41 | prefix = [[0] for _ in xrange(S+1)] 42 | for m in xrange(1, S+1): # sum of harmonic series: O(S/1 + S/2 + ... + S/S) = O(S * (1 + 1/2 + ... + 1/S)) = O(SlogS) 43 | for i in xrange(m, S+1, m): 44 | prefix[m].append(add(prefix[m][-1], slide_hashes[i-1])) 45 | result = [0]*N 46 | curr_hash = 0 47 | for i in xrange(N): 48 | A, L, R, M = raw_input().strip().split() 49 | L, R, M = int(L), int(R), int(M) 50 | curr_hash = HASH_OP[A](curr_hash, get_sum(prefix, L, R, M)) 51 | if curr_hash in lookup: 52 | result[i] = lookup[curr_hash]+1 53 | return " ".join(map(lambda x: str(x) if x else "X", result)) 54 | 55 | seed(0) 56 | MAX_UINT64 = 2**64-1 57 | HASH_OP = {'E':add, 'D':sub} 58 | for case in xrange(input()): 59 | print 'Case #%d: %s' % (case+1, slide_circuits()) 60 | -------------------------------------------------------------------------------- /Virtual World Finals/slide_circuits2.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Virtual World Finals - Problem B. Slide Circuits 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000436329/000000000084f7b2 5 | # 6 | # Time: O(B + N + SlogS) 7 | # Space: O(B + SlogS) 8 | # 9 | # xor of random values solution (faster) 10 | # 11 | 12 | from random import seed, randint 13 | from operator import xor 14 | 15 | def random_id_gen(B, ids_set): 16 | MAX_RAND_ID = MAX_UINT64//(B*2) 17 | for _ in xrange(B): 18 | x = randint(1, MAX_RAND_ID) 19 | while True: 20 | x = randint(1, MAX_RAND_ID) 21 | if x not in ids_set: 22 | break 23 | ids_set.add(x) 24 | yield x 25 | 26 | def get_sum(prefix, L, R, M): 27 | return sub(prefix[M][R//M], prefix[M][(L-1)//M]) 28 | 29 | def slide_circuits(): 30 | B, S, N = map(int, raw_input().strip().split()) 31 | ids_set = set() 32 | in_ids = [i for i in random_id_gen(B, ids_set)] 33 | out_ids = [i for i in random_id_gen(B, ids_set)] 34 | total_hash = add(reduce(add, in_ids), reduce(add, out_ids)) 35 | slide_hashes = [0]*S 36 | lookup = {} 37 | for i in xrange(S): 38 | X, Y = map(int, raw_input().strip().split()) 39 | slide_hashes[i] = add(out_ids[X-1], in_ids[Y-1]) 40 | lookup[sub(total_hash, slide_hashes[i])] = i # 0-indexed 41 | prefix = [[0] for _ in xrange(S+1)] 42 | for m in xrange(1, S+1): # sum of harmonic series: O(S/1 + S/2 + ... + S/S) = O(S * (1 + 1/2 + ... + 1/S)) = O(SlogS) 43 | for i in xrange(m, S+1, m): 44 | prefix[m].append(add(prefix[m][-1], slide_hashes[i-1])) 45 | result = [0]*N 46 | total = curr_hash = 0 47 | for i in xrange(N): 48 | A, L, R, M = raw_input().strip().split() 49 | L, R, M = int(L), int(R), int(M) 50 | total = TOTAL_OP[A](total, R//M-(L-1)//M) 51 | curr_hash = HASH_OP[A](curr_hash, get_sum(prefix, L, R, M)) 52 | if total == B-1 and curr_hash in lookup: # check total to avoid hash collision 53 | result[i] = lookup[curr_hash]+1 54 | return " ".join(map(lambda x: str(x) if x else "X", result)) 55 | 56 | seed(0) 57 | MAX_UINT64 = 2**64-1 58 | add = sub = xor 59 | TOTAL_OP = {'E':lambda a,b: a+b, 'D':lambda a, b:a-b} 60 | HASH_OP = {'E':add, 'D':sub} 61 | for case in xrange(input()): 62 | print 'Case #%d: %s' % (case+1, slide_circuits()) 63 | -------------------------------------------------------------------------------- /Round 1C/double_or_noting2.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Round 1C - Problem C. Double or NOTing 4 | # https://codingcompetitions.withgoogle.com/codejam/round/00000000004362d7/00000000007c1139 5 | # 6 | # Time: O(|E| + K * |S|), K is the number of bit groups of S 7 | # Space: O(|E| + |S|) 8 | # 9 | 10 | # from re import match 11 | from collections import deque 12 | 13 | def logical_flip(s, flag): 14 | while s and s[0]^flag == 1: 15 | s.popleft() 16 | if not s: 17 | s.append(0^(1^flag)) 18 | 19 | def compare(s, e, flag): 20 | if len(s) > len(e): 21 | return False 22 | for i in xrange(len(s)): 23 | if s[i]^flag != e[i]: 24 | return False 25 | return True 26 | 27 | def init_flip_count(E): 28 | s = list(E)+[0] # if s ends with '1', it requires one more "not" operation (flip), which could be easily counted by appending a '0' 29 | suffix_flip_cnt = [0]*len(s) 30 | for i in reversed(xrange(len(s)-1)): 31 | suffix_flip_cnt[i] = suffix_flip_cnt[i+1] + int(s[i] != s[i+1]) 32 | return suffix_flip_cnt 33 | 34 | def get_flip_count(suffix_flip_cnt, i): 35 | return suffix_flip_cnt[i] if i < len(suffix_flip_cnt) else 0 36 | 37 | def find_prefix_and_count(S, E, suffix_flip_cnt): 38 | result = float("inf") 39 | X = 0 40 | while S[0] != 0^(X%2): 41 | if compare(S, E, X%2) and X >= get_flip_count(suffix_flip_cnt, len(S)): 42 | return X+(len(E)-len(S)), None 43 | logical_flip(S, X%2) 44 | X += 1 45 | return result, X 46 | 47 | def double_or_noting(): 48 | S, E = map(lambda x: deque(int(c) for c in list(x)), raw_input().strip().split()) 49 | 50 | suffix_flip_cnt = init_flip_count(E) 51 | result, X = find_prefix_and_count(S, E, suffix_flip_cnt) 52 | if result != float("inf"): 53 | return result 54 | if X >= get_flip_count(suffix_flip_cnt, 0): 55 | return X+len(E)-int(E[0] == 0) 56 | cnt = get_flip_count(suffix_flip_cnt, 1) 57 | if cnt == 0: 58 | # assert(match("^10*$", E)) 59 | return X+1+(len(E)-1) # S =X=> "0" =1=> "1" =(len(E)-1)=> "10*" 60 | if cnt == 1: 61 | # assert(match("^11+0*$", E)) 62 | return X+1+len(E)+1 # S =X=> "0" =1=> "1" =k=> "100+" =1=> "11+" =(len(E)-k)=> "11+0*", where 2 <= k <= len(E) 63 | return "IMPOSSIBLE" 64 | 65 | for case in xrange(input()): 66 | print 'Case #%d: %s' % (case+1, double_or_noting()) 67 | -------------------------------------------------------------------------------- /Round 1A/prime_time.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Round 1A - Problem B. Prime Time 4 | # https://codingcompetitions.withgoogle.com/codejam/round/000000000043585d/00000000007543d8 5 | # 6 | # Time: O((MAX_P * logX) * (M + logX)), X is the sum of all cards 7 | # Space: O(1) 8 | # 9 | 10 | from collections import OrderedDict 11 | 12 | def max_card_number_of_group2(X, count): # Time: O(logX) 13 | # return (X-1).bit_length() # ceil_log2_X, estimated max_card_number_of_group2 14 | result, prod = 0, 1 15 | for p in count.iterkeys(): # use count.iterkeys() instead of count.iteritems() to avoid TLE 16 | for _ in xrange(count[p]): 17 | if prod*p > X: 18 | return result 19 | prod *= p 20 | result += 1 21 | return result 22 | 23 | def estimated_max_card_sum_of_group2(X, count): # Time: O(logX) 24 | # return max_card_number_of_group2(X, count) * next(reversed(count)) # less accurate estimated_max_card_sum_of_group2 25 | result, remain = 0, max_card_number_of_group2(X, count) # Time: O(logX) 26 | for p in reversed(count): 27 | result += p*min(remain, count[p]) 28 | remain -= min(remain, count[p]) 29 | if remain == 0: 30 | break 31 | return result # more accurate but may not be the lower bound of max_card_sum_of_group2 32 | 33 | # given prod = p1*p2*...*pk, check if 34 | # (1) p1+p2+...+pk = total 35 | # (2) numbers of p1,p2,...pk are within the given count limit 36 | def check(prod, total, count): # Time: O(M + logX) 37 | for p in count.iterkeys(): # use count.iterkeys() instead of count.iteritems() to avoid TLE 38 | for _ in xrange(count[p]): 39 | if prod%p != 0: 40 | break 41 | prod //= p # at most O(logX) times 42 | total -= p 43 | if total < 0: # early return 44 | return False 45 | return prod == 1 and total == 0 46 | 47 | def prime_time(): 48 | M = input() 49 | count = OrderedDict() 50 | for _ in xrange(M): 51 | P, N = map(int, raw_input().strip().split()) 52 | count[P] = N 53 | X = sum(p*n for p, n in count.iteritems()) 54 | 55 | for i in xrange(1, estimated_max_card_sum_of_group2(X, count)+1): # prune impossible i 56 | if check(X-i, i, count): 57 | return X-i 58 | return 0 59 | 60 | for case in xrange(input()): 61 | print 'Case #%d: %s' % (case+1, prime_time()) 62 | -------------------------------------------------------------------------------- /Virtual World Finals/ropes.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Virtual World Finals - Problem C. Ropes 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000436329/000000000084fad0 5 | # 6 | # Time: O(N^3), pass in PyPy2 but Python2 7 | # Space: O(N^2) 8 | # 9 | # Usage: python interactive_runner.py python3 testing_tool.py 2 -- python ropes.py 10 | # 11 | # push it east solution 12 | # - Z=10, 18322/20000 = 91.610 % 13 | # - Z=14, 18251/20000 = 91.255 % 14 | # 15 | 16 | from sys import stdout 17 | from itertools import imap 18 | 19 | def play(i, j): 20 | print i+1, j+1 # 0-indexed 21 | stdout.flush() 22 | return map(lambda x: int(x)-1, raw_input().strip().split()) # 0-indexed 23 | 24 | def check_result(A_score, B_score): 25 | assert(input() == int(A_score > B_score)) 26 | 27 | def greedy(score_matrix): 28 | # given that opposing team choose the play that yields the maximum possible score for this turn. 29 | # if there is a tie: 30 | # - they will choose one at random as problem description claims 31 | # - they will not choose by a counter-greedy strategy (they choose as far east as possible), since we can never win by this greedy strategy if they can always do that 32 | result = None 33 | best_score = max(imap(max, score_matrix)) 34 | for i in xrange(2*N): 35 | for j in xrange(2*N): 36 | if score_matrix[i][j] == best_score and (result is None or (sum(result) > i+j)): # we choose as far west as possible if there is a tie 37 | result = (i, j) 38 | return result 39 | 40 | def update(i, j, score_matrix): 41 | for r in xrange(2*N): 42 | for c in xrange(2*N): 43 | if r == i or c == j: 44 | score_matrix[r][c] = NEG_INF 45 | elif (r-i)*(c-j) < 0: 46 | score_matrix[r][c] += 1 47 | 48 | def ropes(): 49 | score_matrix = [[0 for _ in xrange(2*N)] for _ in xrange(2*N)] 50 | A_score = B_score = 0 51 | for k in xrange(N): 52 | i, j = greedy(score_matrix) if k else (Z-1, Z-1) # tuned by experitments that i=j at the first play is better 53 | A_score += score_matrix[i][j] 54 | update(i, j, score_matrix) 55 | i, j = play(i, j) 56 | B_score += score_matrix[i][j] 57 | update(i, j, score_matrix) 58 | check_result(A_score, B_score) 59 | 60 | NEG_INF = float("-inf") 61 | Z = 10 # tuned by experiments that top 4 win rates are with Z = [10, 14, 9, 11] 62 | T, N, W = map(int, raw_input().strip().split()) 63 | for case in xrange(T): 64 | ropes() 65 | -------------------------------------------------------------------------------- /Round 2/retiling.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Round 2 - Problem D. Retiling 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000435915/00000000007dc2de 5 | # 6 | # Time: O((R * C)^3) 7 | # Space: O((R * C)^2) 8 | # 9 | 10 | # Template translated from: 11 | # https://github.com/kth-competitive-programming/kactl/blob/main/content/graph/WeightedMatching.h 12 | # Time: O(N^2 * M) 13 | # Space: O(N + M) 14 | def hungarian(a): 15 | if not a: 16 | return 0, [] 17 | n, m = len(a)+1, len(a[0])+1 18 | u, v, p, ans = [0]*n, [0]*m, [0]*m, [0]*(n-1) 19 | for i in xrange(1, n): 20 | p[0] = i 21 | j0 = 0 # add "dummy" worker 0 22 | dist, pre = [float("inf")]*m, [-1]*m 23 | done = [False]*(m+1) 24 | while True: # dijkstra 25 | done[j0] = True 26 | i0, j1, delta = p[j0], None, float("inf") 27 | for j in xrange(1, m): 28 | if done[j]: 29 | continue 30 | cur = a[i0-1][j-1]-u[i0]-v[j] 31 | if cur < dist[j]: 32 | dist[j], pre[j] = cur, j0 33 | if dist[j] < delta: 34 | delta, j1 = dist[j], j 35 | for j in xrange(m): 36 | if done[j]: 37 | u[p[j]] += delta 38 | v[j] -= delta 39 | else: 40 | dist[j] -= delta 41 | j0 = j1 42 | if not p[j0]: 43 | break 44 | while j0: # update alternating path 45 | j1 = pre[j0] 46 | p[j0], j0 = p[j1], j1 47 | for j in xrange(1, m): 48 | if p[j]: 49 | ans[p[j]-1] = j-1 50 | return -v[0], ans # min cost 51 | 52 | def retiling(): 53 | R, C, F, S = map(int, raw_input().strip().split()) 54 | src, dst = [[raw_input().strip() for _ in xrange(R)] for _ in xrange(2)] 55 | pos0 = [(i, j) for i in xrange(R) for j in xrange(C) if src[i][j] == 'M'] 56 | pos1 = [(i, j) for i in xrange(R) for j in xrange(C) if dst[i][j] == 'M'] 57 | cost = [[0]*(len(pos0)+len(pos1)) for _ in xrange(len(pos0)+len(pos1))] 58 | for i in xrange(len(cost)): 59 | for j in xrange(len(cost[0])): 60 | if i < len(pos0) and j < len(pos1): 61 | cost[i][j] = S * (abs(pos0[i][0]-pos1[j][0])+abs(pos0[i][1]-pos1[j][1])) 62 | elif i < len(pos0) or j < len(pos1): 63 | cost[i][j] = F 64 | return hungarian(cost)[0] 65 | 66 | for case in xrange(input()): 67 | print 'Case #%d: %s' % (case+1, retiling()) 68 | -------------------------------------------------------------------------------- /Virtual World Finals/cutting_cake.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Virtual World Finals - Problem A. Cutting Cake 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000436329/000000000084fba1 5 | # 6 | # Time: O(NlogN) 7 | # Space: O(N) 8 | # 9 | 10 | from fractions import Fraction 11 | 12 | def ccw(A, B, C): 13 | return (B[0]-A[0])*(C[1]-A[1]) - (B[1]-A[1])*(C[0]-A[0]) 14 | 15 | def find_delta_slopes(points): 16 | delta_slopes = [Fraction(0)]*3 17 | for i in xrange(len(points)): 18 | if not points[i][0]-points[i-1][0]: 19 | continue 20 | slope = Fraction(points[i][1]-points[i-1][1], points[i][0]-points[i-1][0]) 21 | delta_slopes[i-1] += slope 22 | delta_slopes[i] -= slope 23 | assert(sum(delta_slopes) == 0) 24 | return delta_slopes 25 | 26 | def cutting_cake(): 27 | N, W, H = map(int, raw_input().strip().split()) 28 | P, Q, R, S = map(Fraction, raw_input().strip().split()) 29 | points = sorted([(Fraction(0), Fraction(0)), (P, Q), (R, S)]) 30 | prev_diff, area = Fraction(0), Fraction(-ccw(*points), 2) 31 | events = [] 32 | for _ in xrange(N): 33 | X, Y, A, B = map(Fraction, raw_input().strip().split()) 34 | prev_diff -= B*area 35 | for i in xrange(len(points)): 36 | events.append((X+points[i][0], i, A+B)) 37 | events.sort() 38 | 39 | result = abs(prev_diff) 40 | delta_y = [points[i][1]-points[i-1][1] if points[i-1][0] == points[i][0] else 0 for i in xrange(len(points))] 41 | delta_slopes = find_delta_slopes(points) 42 | prev_x = prev_y = slope = Fraction(0) 43 | for curr_x, i, w in events: 44 | dx = curr_x-prev_x 45 | if not dx: 46 | prev_y += w*delta_y[i] 47 | slope += w*delta_slopes[i] 48 | continue 49 | curr_y = prev_y+dx*slope 50 | curr_diff = prev_diff+(prev_y+curr_y)/2*dx 51 | result = min(result, abs(curr_diff)) # the values at each endpoint of the interval 52 | extreme_diff = prev_diff 53 | if curr_y*prev_y < 0: # find the value at the extreme point of the quadratic if that's within the interval 54 | extreme_y = Fraction(0) 55 | extreme_x = (extreme_y-prev_y)/slope+prev_x 56 | extreme_diff = prev_diff+(prev_y+extreme_y)/2*(extreme_x-prev_x) 57 | result = min(result, abs(extreme_diff)) 58 | if min(extreme_diff, prev_diff, curr_diff) <= 0 <= max(extreme_diff, prev_diff, curr_diff): # whether the quadratic crosses 0 within the interval 59 | result = Fraction(0) 60 | break 61 | prev_diff, prev_x, prev_y = curr_diff, curr_x, curr_y 62 | slope += w*delta_slopes[i] 63 | result /= abs(area) 64 | return "%s/%s"%(result.numerator, result.denominator) 65 | 66 | for case in xrange(input()): 67 | print 'Case #%d: %s' % (case+1, cutting_cake()) 68 | -------------------------------------------------------------------------------- /Round 1A/hacked_exam.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Round 1A - Problem C. Hacked Exam 4 | # https://codingcompetitions.withgoogle.com/codejam/round/000000000043585d/0000000000754750 5 | # 6 | # Time: precompute: O(MAX_Q^2) 7 | # runtime: O(Q) 8 | # Space: O(MAX_Q^2), for nCr cache 9 | # 10 | 11 | from itertools import izip 12 | from fractions import gcd 13 | 14 | def hacked_exam(): 15 | N, Q = map(int, raw_input().strip().split()) 16 | A, S = [], [] 17 | for _ in xrange(N): 18 | a, s = raw_input().strip().split() 19 | A.append(a) 20 | S.append(int(s)) 21 | while N < 3: # duplicate until N = 3 22 | A.append(A[-1]) 23 | S.append(S[-1]) 24 | N += 1 25 | for _ in xrange(len(nCr), Q+1): # cached nCr, O(MAX_Q^2) time in total 26 | nCr.append([1] + [nCr[-1][i] + nCr[-1][i+1] for i in xrange(len(nCr[-1])-1)] + [1]) 27 | 28 | an = sum(a == b == c for a, b, c in izip(*A)) 29 | bn = sum(b == c != a for a, b, c in izip(*A)) 30 | cn = sum(c == a != b for a, b, c in izip(*A)) 31 | dn = sum(a == b != c for a, b, c in izip(*A)) 32 | total = acount = bcount = ccount = dcount = 0 33 | for ar in xrange(an+1): 34 | # (1) ar + (bn-br) + cr + dr = S[0] 35 | # (2) ar + br + (cn-cr) + dr = S[1] 36 | # (3) ar + br + cr + (dn-dr) = S[2] 37 | br = (S[1]+S[2]-cn-dn)//2-ar # [(2)+(3)]/2, since at least one br exists and (S[1]+S[2]-cn-dn)//2 is constant, so (S[1]+S[2]-cn-dn)%2 is always 0 38 | cr = (S[2]+S[0]-bn-dn)//2-ar # [(3)+(1)]/2, since at least one cr exists and (S[2]+S[0]-bn-dn)//2 is constant, so (S[2]+S[0]-bn-dn)%2 is always 0 39 | dr = (S[0]+S[1]-bn-cn)//2-ar # [(1)+(2)]/2, since at least one dr exists and (S[0]+S[1]-bn-cn)//2 is constant, so (S[0]+S[1]-bn-cn)%2 is always 0 40 | if not (0 <= br <= bn and 0 <= cr <= cn and 0 <= dr <= dn): 41 | continue # ar is invalid 42 | ways = nCr[an][ar] * nCr[bn][br] * nCr[cn][cr] * nCr[dn][dr] 43 | total += ways 44 | acount += ways*ar 45 | bcount += ways*br 46 | ccount += ways*cr 47 | dcount += ways*dr 48 | result = [] 49 | for a, b, c in izip(*A): 50 | if a == b == c: 51 | result.append(a if acount >= total*an-acount else "TF"[a == 'T']) 52 | elif b == c != a: 53 | result.append(b if bcount >= total*bn-bcount else a) 54 | elif c == a != b: 55 | result.append(c if ccount >= total*cn-ccount else b) 56 | elif a == b != c: 57 | result.append(a if dcount >= total*dn-dcount else c) 58 | count = max(acount, total*an-acount) + max(bcount, total*bn-bcount) + max(ccount, total*cn-ccount) + max(dcount, total*dn-dcount) 59 | g = gcd(count, total) 60 | return "%s %s/%s" % ("".join(result), count//g, total//g) 61 | 62 | nCr = [[1]] 63 | for case in xrange(input()): 64 | print 'Case #%d: %s' % (case+1, hacked_exam()) 65 | -------------------------------------------------------------------------------- /Round 1B/digit_blocks.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Round 1B - Problem C. Digit Blocks 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000435baf/00000000007ae37b 5 | # 6 | # Time: precompute: O(N^3 * B * D) 7 | # runtime: O(N * B) 8 | # Space: O(N^3 * B * D) 9 | # 10 | # Usage: python interactive_runner.py python3 testing_tool.py 1 -- python digit_blocks.py 11 | # 12 | # Expected score compared to the max expected score = 19086952424670896.00/19131995794056374.42 = 99.76% 13 | # 14 | 15 | from sys import stdout 16 | 17 | def read(): 18 | return input() 19 | 20 | def write(i): 21 | print i 22 | stdout.flush() 23 | 24 | def digit_blocks(): 25 | grow_h = 0 26 | lookup = [[] for _ in xrange(B+1)] 27 | lookup[0] = range(N) 28 | for _ in xrange(N*B): 29 | d = read() 30 | h = choice[len(lookup[B])][len(lookup[B-1])][len(lookup[B-2])][grow_h][d] 31 | if h < B-2: 32 | grow_h = (grow_h+1)%(B-2) 33 | lookup[h+1].append(lookup[h].pop()) 34 | write(lookup[h+1][-1]+1) 35 | 36 | D = 10 37 | T, N, B, P = map(int, raw_input().strip().split()) 38 | P = [1] 39 | while len(P) < B: 40 | P.append(P[-1]*D) 41 | dp = [[[[0.0 for _ in xrange(B-2)] for _ in xrange(N+1)] for _ in xrange(N+1)] for _ in xrange(N+1)] 42 | choice = [[[[[None for _ in xrange(D)] for _ in xrange(B-2)] for _ in xrange(N+1)] for _ in xrange(N+1)] for _ in xrange(N+1)] 43 | for remain0_cnt in reversed(xrange(N)): 44 | for remain1_cnt in reversed(xrange(N-remain0_cnt+1)): 45 | for remain2_cnt in reversed(xrange(N-remain0_cnt-remain1_cnt+1)): 46 | for grow_h in reversed(xrange(1 if remain0_cnt+remain1_cnt+remain2_cnt == N else B-2)): 47 | for d in xrange(D): 48 | max_ev = float("-inf") 49 | if remain1_cnt: 50 | ev = dp[remain0_cnt+1][remain1_cnt-1][remain2_cnt][grow_h] + P[B-1]*d 51 | if ev > max_ev: 52 | max_ev = ev 53 | choice[remain0_cnt][remain1_cnt][remain2_cnt][grow_h][d] = B-1 54 | if remain2_cnt: 55 | ev = dp[remain0_cnt][remain1_cnt+1][remain2_cnt-1][grow_h] + P[B-2]*d 56 | if ev > max_ev: 57 | max_ev = ev 58 | choice[remain0_cnt][remain1_cnt][remain2_cnt][grow_h][d] = B-2 59 | if remain0_cnt+remain1_cnt+remain2_cnt != N: 60 | ev = dp[remain0_cnt][remain1_cnt][remain2_cnt+(grow_h+1)//(B-2)][(grow_h+1)%(B-2)] + P[grow_h]*d 61 | if ev > max_ev: 62 | max_ev = ev 63 | choice[remain0_cnt][remain1_cnt][remain2_cnt][grow_h][d] = grow_h 64 | dp[remain0_cnt][remain1_cnt][remain2_cnt][grow_h] += max_ev/D 65 | S = 19131995794056374.42 66 | assert(dp[0][0][0][0]/S >= 0.9976) 67 | for case in xrange(T): 68 | digit_blocks() 69 | -------------------------------------------------------------------------------- /Round 3/build_a_pair3.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Round 3 - Problem A. Build-A-Pair 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000436142/0000000000813aa8 5 | # 6 | # Time: O(b^2 * N), b = 10 7 | # Space: O(b) 8 | # 9 | # optimized from build_a_pair2.py 10 | # 11 | 12 | def greedy(n, l, count, dir): # Time: O(N) 13 | for i in dir(xrange(len(count))): 14 | if count[i] == 0: 15 | continue 16 | common = min(l, count[i]) 17 | l -= common 18 | count[i] -= common 19 | for _ in xrange(common): 20 | n = 10*n + i 21 | return n 22 | 23 | def odd_case(count): # Time: O(N) 24 | d = next(d for d in xrange(1, len(count)) if count[d]) 25 | count[d] -= 1 26 | remain = sum(count) 27 | A = greedy(d, remain//2, count, lambda x: x) 28 | B = greedy(0, remain//2, count, reversed) 29 | return A-B 30 | 31 | def even_case(count): # Time: O(b^2 * N) 32 | result = float("inf") 33 | for d in xrange(-1, len(count)): # other than the shared prefix, there is no digit with any pair, or a digit with 1 pair, or digit 0 with all pairs, O(b) times 34 | if d != -1 and count[d] < 2: 35 | continue 36 | new_counts = [[count[j]%2 if j != d else (2+count[d]%2 if i != 1 else count[d]) for j in xrange(len(count))] for i in xrange(2 if d == 0 else 1)] 37 | for new_count in new_counts: # at most O(2) times 38 | has_prefix = True 39 | if all(new_count[k] == count[k] for k in xrange(1, len(count))): # no digit other than 0 is chosen, O(b) times 40 | if new_count[0] != count[0]: # invalid 41 | continue 42 | has_prefix = False 43 | candidates = [k for k, v in enumerate(new_count) if v and (k or has_prefix)] 44 | if not candidates: 45 | return 0 46 | if len(candidates) == 1: 47 | continue 48 | remain = sum(new_count) 49 | min_diff = min(candidates[i]-candidates[i-1] for i in xrange(1, len(candidates))) 50 | for i in xrange(1, len(candidates)): # O(b) times 51 | a, b = candidates[i], candidates[i-1] 52 | if new_count[b] == 0 or a-b != min_diff: # for each a, b s.t. a-b > min_diff, where any A-B won't be the result 53 | continue 54 | tmp_count = new_count[:] 55 | tmp_count[a] -= 1 56 | tmp_count[b] -= 1 57 | A = greedy(a, remain//2-1, tmp_count, lambda x: x) # Time: O(N) 58 | B = greedy(b, remain//2-1, tmp_count, reversed) # Time: O(N) 59 | result = min(result, A-B) 60 | return result 61 | 62 | def build_a_pair(): 63 | D = map(int, list(raw_input().strip())) 64 | 65 | count = [0]*BASE 66 | for c in D: 67 | count[int(c)] += 1 68 | return odd_case(count) if sum(count)%2 == 1 else even_case(count) 69 | 70 | BASE = 10 71 | for case in xrange(input()): 72 | print 'Case #%d: %s' % (case+1, build_a_pair()) 73 | -------------------------------------------------------------------------------- /Virtual World Finals/divisible_divisions.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Virtual World Finals - Problem D. Divisible Divisions 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000436329/000000000084fb3a 5 | # 6 | # Time: O(|S|logD + D) 7 | # Space: O(|S| + D) 8 | # 9 | 10 | from collections import Counter 11 | 12 | def addmod(a, b): 13 | return (a+b)%MOD 14 | 15 | def divisible_divisions(): 16 | S, D = raw_input().strip().split() 17 | S, D = map(int, list(S)), int(D) 18 | 19 | cnts = Counter([1]) 20 | d_remain = D 21 | for p in [2, 5]: 22 | while d_remain%p == 0: 23 | d_remain //= p 24 | cnts[p] += 1 25 | l = max(cnts.itervalues()) # l = O(logD) 26 | 27 | suffix = [0]*(len(S)+1) 28 | basis = 1 29 | for i in reversed(xrange(len(S))): 30 | suffix[i] = (suffix[i+1] + S[i]*basis) % d_remain 31 | basis = basis*10 % d_remain 32 | 33 | # dp1[i]: count of divisible divisions of this prefix whose last division is divisible by D ends at S[i-1] 34 | # dp2[i]: count of divisible divisions of this prefix whose last division is not divisible by D ends at S[i-1] 35 | dp1, dp2 = [[0]*(len(S)+1) for _ in xrange(2)] 36 | dp1[0] = 1 37 | prefix_total, prefix_dp1 = [[0]*d_remain for _ in xrange(2)] 38 | accu_dp1, d_2_5 = 1, D//d_remain 39 | for i in xrange(1, len(S)+1): 40 | dp2[i] = accu_dp1 41 | curr, basis = 0, 1 42 | for k in xrange(1, l+1): # O(logD) times 43 | if i-k < 0: 44 | break 45 | j = i-k 46 | curr = (curr + S[j]*basis) % d_2_5 47 | if k == l: 48 | prefix_total[suffix[j]] = addmod(prefix_total[suffix[j]], addmod(dp1[j], dp2[j])) 49 | prefix_dp1[suffix[j]] = addmod(prefix_dp1[suffix[j]], dp1[j]) 50 | if curr == 0: 51 | # since all(S[j:i]%d_2_5 == 0 for j in xrange(i-l+1)) is true, 52 | # find sum(cnt[j] for j in xrange(i-l+1) if suffix[j] == suffix[i]) <=> find sum(cnt[j] for j in xrange(i-l+1) if S[j:i]%D == 0) 53 | dp1[i] = addmod(dp1[i], prefix_total[suffix[i]]) # prefix_total[suffix[i]] = sum(dp1[j]+dp2[j] for j in xrange(i-l+1) if suffix[j] == suffix[i])%MOD 54 | dp2[i] = addmod(dp2[i], -prefix_dp1[suffix[i]]) # prefix_dp1[suffix[i]] = sum(dp1[j] for j in xrange(i-l+1) if suffix[j] == suffix[i])%MOD 55 | break 56 | if curr == 0 and suffix[j] == suffix[i]: # (S[j:i]%d_2_5 == 0) and (suffix[j]-suffix[i] == 0 <=> (S[j:i]*10^(len(S)-i)%d_remain == 0 and gcd(d_remain, 10^(len(S)-i)) == 1) <=> S[j:i]%d_remain == 0) <=> S[j:i]%D == 0 57 | dp1[i] = addmod(dp1[i], addmod(dp1[j], dp2[j])) 58 | dp2[i] = addmod(dp2[i], -dp1[j]) 59 | basis = basis*10 % d_2_5 60 | accu_dp1 = addmod(accu_dp1, dp1[i]) 61 | return addmod(dp1[len(S)], dp2[len(S)]) 62 | 63 | MOD = 10**9+7 64 | for case in xrange(input()): 65 | print 'Case #%d: %s' % (case+1, divisible_divisions()) 66 | -------------------------------------------------------------------------------- /Round 3/build_a_pair.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Round 3 - Problem A. Build-A-Pair 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000436142/0000000000813aa8 5 | # 6 | # Time: O((N/(2b) + 1)^b * b^2 * N), b = 10, pass in PyPy2 but Python2 7 | # Space: O(b) 8 | # 9 | 10 | from operator import mul 11 | 12 | def greedy(n, l, count, dir): # Time: O(N) 13 | for i in dir(xrange(len(count))): 14 | if count[i] == 0: 15 | continue 16 | common = min(l, count[i]) 17 | l -= common 18 | count[i] -= common 19 | for _ in xrange(common): 20 | n = 10*n + i 21 | return n 22 | 23 | def odd_case(count): # Time: O(N) 24 | d = next(d for d in xrange(1, len(count)) if count[d]) 25 | count[d] -= 1 26 | remain = sum(count) 27 | A = greedy(d, remain//2, count, lambda x: x) 28 | B = greedy(0, remain//2, count, reversed) 29 | return A-B 30 | 31 | def mask_to_count(count, choice, mask): # Time: O(b) 32 | new_count = [0]*BASE 33 | for k, v in enumerate(choice): 34 | mask, cnt = divmod(mask, v) 35 | new_count[k] = cnt*2+count[k]%2 36 | return new_count 37 | 38 | def even_case(count): # Time: O((N/(2b) + 1)^b * b^2 * N) 39 | choice = [0]*BASE 40 | for k, v in enumerate(count): 41 | choice[k] = v//2+1 42 | total = reduce(mul, (v for v in choice if v)) 43 | result = float("inf") 44 | for mask in xrange(total): # enumerate all possible prefixes 45 | # N/2 + b >= (c0+1) + (c1+1) + ... + (c(b-1)+1) >= b * ((c0+1)*(c1+1)*...*(c(b-1)+1))^(1/b) 46 | # (c0+1)*(c1+1)*...*(c(b-1)+1) <= (N/(2b) + 1)^b 47 | # mask loops at most O((N/(2b) + 1)^b) times 48 | has_prefix = True 49 | new_count = mask_to_count(count, choice, mask) 50 | if all(new_count[k] == count[k] for k in xrange(1, len(count))): # no digit other than 0 is chosen 51 | if new_count[0] != count[0]: # invalid 52 | continue 53 | has_prefix = False 54 | candidates = [k for k, v in enumerate(new_count) if v and (k or has_prefix)] 55 | if not candidates: 56 | return 0 57 | if len(candidates) == 1: 58 | continue 59 | remain = sum(new_count) 60 | for i in xrange(1, len(candidates)): # O(b^2) times 61 | for j in xrange(i): 62 | tmp_count = new_count[:] 63 | tmp_count[candidates[i]] -= 1 64 | tmp_count[candidates[j]] -= 1 65 | A = greedy(candidates[i], remain//2-1, tmp_count, lambda x: x) # Time: O(N) 66 | B = greedy(candidates[j], remain//2-1, tmp_count, reversed) # Time: O(N) 67 | result = min(result, A-B) 68 | return result 69 | 70 | def build_a_pair(): 71 | D = map(int, list(raw_input().strip())) 72 | 73 | count = [0]*BASE 74 | for c in D: 75 | count[int(c)] += 1 76 | return odd_case(count) if sum(count)%2 == 1 else even_case(count) 77 | 78 | BASE = 10 79 | for case in xrange(input()): 80 | print 'Case #%d: %s' % (case+1, build_a_pair()) 81 | -------------------------------------------------------------------------------- /Round 1C/double_or_noting3.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Round 1C - Problem C. Double or NOTing 4 | # https://codingcompetitions.withgoogle.com/codejam/round/00000000004362d7/00000000007c1139 5 | # 6 | # Time: O(|E| + |S|) 7 | # Space: O(|E| + |S|) 8 | # 9 | # KMP solution 10 | # 11 | 12 | # from re import match 13 | 14 | def modified_KMP(text, pattern, prefix, flag): 15 | j = -1 16 | for i in xrange(len(text)): 17 | while j != -1 and pattern[j+1] != text[i]^flag: 18 | j = prefix[j] 19 | if pattern[j+1] == text[i]^flag: 20 | j += 1 21 | if j+1 == len(pattern): 22 | if i != len(text)-1: 23 | j = prefix[j] 24 | return j # pattern[:j+1] is the longest suffix of text 25 | 26 | def get_prefix(pattern): 27 | prefix = [-1]*len(pattern) 28 | j = -1 29 | for i in xrange(1, len(pattern)): 30 | while j != -1 and pattern[j+1] != pattern[i]: 31 | j = prefix[j] 32 | if pattern[j+1] == pattern[i]: 33 | j += 1 34 | prefix[i] = j 35 | return prefix 36 | 37 | def init_flip_count(E): 38 | s = E+[0] # if s ends with '1', it requires one more "not" operation (flip), which could be easily counted by appending a '0' 39 | suffix_flip_cnt = [0]*len(s) 40 | for i in reversed(xrange(len(s)-1)): 41 | suffix_flip_cnt[i] = suffix_flip_cnt[i+1]+int(s[i] != s[i+1]) 42 | return suffix_flip_cnt 43 | 44 | def get_flip_count(suffix_flip_cnt, i): 45 | return suffix_flip_cnt[i] if i < len(suffix_flip_cnt) else 0 46 | 47 | def find_X(S): 48 | X_cnt = [-1]*len(S) # only valid positions with X >= 0 49 | X_cnt[0] = X = 0 50 | for i in xrange(1, len(S)): 51 | if S[i] == S[i-1]: 52 | continue 53 | X += 1 54 | X_cnt[i] = X 55 | return X_cnt, X+S[0] 56 | 57 | def find_prefix_and_count(S, E, suffix_flip_cnt): 58 | result = float("inf") 59 | X_cnt, X = find_X(S) 60 | if 0 in [S[0], E[0]]: 61 | return result, X 62 | prefix = get_prefix(E) 63 | for i in xrange(2): 64 | j = modified_KMP(S, E, prefix, i) 65 | while j != -1: 66 | if X_cnt[-1-j] >= get_flip_count(suffix_flip_cnt, j+1): # S[-1-j:] is the same structure as E[:j+1] and its X >= flip_cnt(E[j+1:]) 67 | result = min(result, X_cnt[-1-j]+(len(E)-(j+1))) 68 | break 69 | j = prefix[j] 70 | return result, X 71 | 72 | def double_or_noting(): 73 | S, E = map(lambda x: [int(c) for c in list(x)], raw_input().strip().split()) 74 | 75 | suffix_flip_cnt = init_flip_count(E) 76 | result, X = find_prefix_and_count(S, E, suffix_flip_cnt) 77 | if result != float("inf"): 78 | return result 79 | if X >= get_flip_count(suffix_flip_cnt, 0): 80 | return X+len(E)-int(E[0] == 0) 81 | cnt = get_flip_count(suffix_flip_cnt, 1) 82 | if cnt == 0: 83 | # assert(match("^10*$", E)) 84 | return X+1+(len(E)-1) # S =X=> "0" =1=> "1" =(len(E)-1)=> "10*" 85 | if cnt == 1: 86 | # assert(match("^11+0*$", E)) 87 | return X+1+len(E)+1 # S =X=> "0" =1=> "1" =k=> "100+" =1=> "11+" =(len(E)-k)=> "11+0*", where 2 <= k <= len(E) 88 | return "IMPOSSIBLE" 89 | 90 | for case in xrange(input()): 91 | print 'Case #%d: %s' % (case+1, double_or_noting()) 92 | -------------------------------------------------------------------------------- /Round 3/build_a_pair2.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Round 3 - Problem A. Build-A-Pair 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000436142/0000000000813aa8 5 | # 6 | # Time: O(3^b * b * N), b = 10, pass in PyPy2 but Python2 7 | # Space: O(b) 8 | # 9 | # optimized from build_a_pair.py 10 | # 11 | 12 | from operator import mul 13 | 14 | def greedy(n, l, count, dir): # Time: O(N) 15 | for i in dir(xrange(len(count))): 16 | if count[i] == 0: 17 | continue 18 | common = min(l, count[i]) 19 | l -= common 20 | count[i] -= common 21 | for _ in xrange(common): 22 | n = 10*n + i 23 | return n 24 | 25 | def odd_case(count): # Time: O(N) 26 | d = next(d for d in xrange(1, len(count)) if count[d]) 27 | count[d] -= 1 28 | remain = sum(count) 29 | A = greedy(d, remain//2, count, lambda x: x) 30 | B = greedy(0, remain//2, count, reversed) 31 | return A-B 32 | 33 | def mask_to_count(count, choice, mask): # Time: O(b) 34 | new_count = [0]*BASE 35 | for k, v in enumerate(choice): 36 | mask, cnt = divmod(mask, v) 37 | new_count[k] = cnt*2+count[k]%2 if cnt != CHOICE-1 else count[k] 38 | return new_count 39 | 40 | def even_case(count): # Time: O(3^b * b * N) 41 | choice = [0]*BASE 42 | for k, v in enumerate(count): 43 | choice[k] = min(v//2+1, CHOICE) 44 | total = reduce(mul, (v for v in choice if v)) 45 | result = float("inf") 46 | for mask in xrange(total): # enumerate all possible prefixes, loops O(3^b) times 47 | has_prefix = True 48 | new_count = mask_to_count(count, choice, mask) 49 | if all(new_count[k] == count[k] for k in xrange(1, len(count))): # no digit other than 0 is chosen 50 | if new_count[0] != count[0]: # invalid 51 | continue 52 | has_prefix = False 53 | candidates = [k for k, v in enumerate(new_count) if v and (k or has_prefix)] 54 | if not candidates: 55 | return 0 56 | if len(candidates) == 1: 57 | continue 58 | remain = sum(new_count) 59 | min_diff = min(candidates[i]-candidates[i-1] for i in xrange(1, len(candidates))) 60 | for i in xrange(1, len(candidates)): # O(b) times 61 | a, b = candidates[i], candidates[i-1] 62 | if new_count[b] == 0 or a-b != min_diff: # for each a, b s.t. a-b > min_diff, where any A-B won't be the result 63 | continue 64 | tmp_count = new_count[:] 65 | tmp_count[a] -= 1 66 | tmp_count[b] -= 1 67 | A = greedy(a, remain//2-1, tmp_count, lambda x: x) # Time: O(N) 68 | B = greedy(b, remain//2-1, tmp_count, reversed) # Time: O(N) 69 | result = min(result, A-B) 70 | return result 71 | 72 | def build_a_pair(): 73 | D = map(int, list(raw_input().strip())) 74 | 75 | count = [0]*BASE 76 | for c in D: 77 | count[int(c)] += 1 78 | return odd_case(count) if sum(count)%2 == 1 else even_case(count) 79 | 80 | CHOICE = 3 # other than the shared prefix, since keeping 1 pair may happen in some cases, for each digit, we have 3 choices to keep: 0 pair, 1 pair, all pairs 81 | BASE = 10 82 | for case in xrange(input()): 83 | print 'Case #%d: %s' % (case+1, build_a_pair()) 84 | -------------------------------------------------------------------------------- /Round 3/square_free.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Round 3 - Problem B. Square Free 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000436142/0000000000813e1a 5 | # 6 | # Time: O(R^2 * C^2) 7 | # Space: O(R + C) 8 | # 9 | 10 | def inplace_counting_sort(nums, reverse=False): # Time: O(len(nums)+max(nums)), Space: O(max(nums)) 11 | count = [0]*(max(nums)+1) 12 | for num in nums: 13 | count[num] += 1 14 | for i in xrange(1, len(count)): 15 | count[i] += count[i-1] 16 | for i in reversed(xrange(len(nums))): # inplace but unstable sort 17 | if nums[i] < 0: # processed 18 | continue 19 | while i != count[nums[i]]-1: 20 | count[nums[i]] -= 1 21 | nums[count[nums[i]]], nums[i] = ~nums[i], nums[count[nums[i]]] 22 | count[nums[i]] -= 1 23 | nums[i] = ~nums[i] 24 | for i in xrange(len(nums)): 25 | nums[i] = ~nums[i] # restore values 26 | if reverse: # unstable sort 27 | nums.reverse() 28 | 29 | def possible(S, D): # Time: O(R * C), Space: O(R + C) 30 | inplace_counting_sort(S, reverse=True) # Time: O(R + C), Space: O(C) 31 | inplace_counting_sort(D, reverse=True) # Time: O(R + C), Space: O(R) 32 | S_prefix = [0] 33 | for i in xrange(len(S)): # Time: O(R), Space: O(R) 34 | S_prefix.append(S_prefix[-1] + S[i]) 35 | D_suffix = [0] 36 | for i in reversed(xrange(len(D))): # Time: O(C), Space: O(C) 37 | D_suffix.append(D_suffix[-1] + D[i]) 38 | D_suffix.reverse() 39 | # consider a graph running max flow algorithm where edge from source to each Sx is with capacity S[x], edge from each Sx to each Dy is with capacity 1, edge from each Dy to sink is with capacity D[y], 40 | # if sum(S) != sum(D), it is impossible, 41 | # otherwise, we want all nodes with full capacity, 42 | # it is possible 43 | # <=> sum(S[x] for x in X)-sum(D[y] for y in Y) <= |X|*(C-|Y|) for all 0 <= |X| <= R and 0 <= |Y| <= C 44 | # <=> sum(S[x] for x in X')-sum(D[y] for y in Y') <= |X|*|Y| for all 0 <= |X| <= R and 0 <= |Y| <= C 45 | # and X' is the biggist |X| of S and Y' is the smallest C-|Y| of D 46 | # <=> -(sum(S)-sum(S[x] for x in X'))+(sum(D)-sum(D[y]) for y in Y') <= |X|*|Y| for all 0 <= |X| <= R and 0 <= |Y| <= C 47 | # and X' is the biggist |X| of S and Y' is the smallest C-|Y| of D 48 | # <=> sum(D[y] for y in Y'')-sum(S[x] for x in X'') <= |X|*|Y| for all 0 <= |X| <= R and 0 <= |Y| <= C 49 | # and Y'' is the biggest |Y| of D and X'' is the smallest R-|X| of S 50 | return S_prefix[-1] == D_suffix[0] and \ 51 | all(S_prefix[i]-D_suffix[j] <= i*j for i in xrange(len(S_prefix)) for j in xrange(len(D_suffix))) # Time: O(R * C) 52 | 53 | def square_free(): 54 | R, C = map(int, raw_input().strip().split()) 55 | S = map(lambda x: C-int(x), raw_input().strip().split()) 56 | D = map(lambda x: R-int(x), raw_input().strip().split()) 57 | 58 | if not possible(S[:], D[:]): 59 | return "IMPOSSIBLE" 60 | result = [['/']*C for _ in xrange(R)] 61 | for i in xrange(R): 62 | for j in xrange(C): 63 | if not (S[i] >= 1 and D[j] >= 1 and possible([S[k]-int(k == i) for k in xrange(len(S))], [D[k]-int(k == j) for k in xrange(len(D))])): 64 | continue 65 | result[i][j] = '\\' # lexicographically smallest, assumed '\\' < '/' 66 | S[i], D[j] = S[i]-1, D[j]-1 67 | return "POSSIBLE\n"+"\n".join("".join(row) for row in result) 68 | 69 | for case in xrange(input()): 70 | print 'Case #%d: %s' % (case+1, square_free()) 71 | -------------------------------------------------------------------------------- /Virtual World Finals/divisible_divisions2.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Virtual World Finals - Problem D. Divisible Divisions 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000436329/000000000084fb3a 5 | # 6 | # Time: O(|S|logD) 7 | # Space: O(min(|S|logD, D)) 8 | # 9 | # Time and Space optimized from divisible_divisions.py 10 | # 11 | 12 | from collections import Counter 13 | 14 | def addmod(a, b): 15 | return (a+b)%MOD 16 | 17 | # Template: 18 | # https://github.com/kamyu104/GoogleCodeJam-2021/blob/main/Round%201B/broken_clock.py 19 | def linear_congruence(a, m, b): # Time: O(logN), the same as gcd, Space: O(logN) 20 | # gcd(a, m) = g and g|b, find x, s.t. ax % m = b % m 21 | # => (a%m)x = my+(b%m) 22 | # => gcd(m, a%m) = g and g|-(b%m), find y, s.t. my % (a%m) = -(b%m) % (a%m) 23 | # => y = linear_congruence(m, a%m, -(b%m)) 24 | # => x = (my+(b%m))/(a%m) 25 | ambs = [] 26 | while m: 27 | a, m, b = m, a%m, -(b%m) 28 | if m: 29 | ambs.append((m, a, -b)) 30 | x = a # a is gcd 31 | while ambs: 32 | a, m, b = ambs.pop() 33 | x = (m*x+b)//a 34 | return x 35 | 36 | def divisible_divisions(): 37 | S, D = raw_input().strip().split() 38 | S, D = map(int, list(S)), int(D) 39 | 40 | cnts = Counter([1]) 41 | d_remain = D 42 | for p in [2, 5]: 43 | while d_remain%p == 0: 44 | d_remain //= p 45 | cnts[p] += 1 46 | l = max(cnts.itervalues()) # l = O(logD) 47 | 48 | curr1, basis1 = 0, 1 49 | for i in reversed(xrange(len(S))): 50 | curr1 = (curr1 + S[i]*basis1) % d_remain 51 | basis1 = basis1*10 % d_remain 52 | 53 | # dp1[i]: count of divisible divisions of this prefix whose last division is divisible by D ends at S[i-1] 54 | # dp2[i]: count of divisible divisions of this prefix whose last division is not divisible by D ends at S[i-1] 55 | w = l+1 56 | dp1, dp2, suffix = [[0]*w for _ in xrange(3)] 57 | dp1[0], suffix[0] = 1, curr1 58 | prefix_total, prefix_dp1 = [Counter() for _ in xrange(2)] 59 | accu_dp1, d_2_5 = 1, D//d_remain 60 | inv_10_mod_d_remain = linear_congruence(10, d_remain, 1)%d_remain # Time: O(logD) 61 | for i in xrange(1, len(S)+1): 62 | basis1 = basis1*inv_10_mod_d_remain % d_remain 63 | curr1 = (curr1 - S[i-1]*basis1) % d_remain 64 | dp1[i%w], dp2[i%w], suffix[i%w] = 0, accu_dp1, curr1 65 | curr2, basis2 = 0, 1 66 | for k in xrange(1, l+1): # O(logD) times 67 | if i-k < 0: 68 | break 69 | j = i-k 70 | curr2 = (curr2 + S[j]*basis2) % d_2_5 71 | if k == l: 72 | prefix_total[suffix[j%w]] = addmod(prefix_total[suffix[j%w]], addmod(dp1[j%w], dp2[j%w])) 73 | prefix_dp1[suffix[j%w]] = addmod(prefix_dp1[suffix[j%w]], dp1[j%w]) 74 | if curr2 == 0: 75 | # since all(S[j:i]%d_2_5 == 0 for j in xrange(i-l+1)) is true, 76 | # find sum(cnt[j] for j in xrange(i-l+1) if suffix[j] == suffix[i]) <=> find sum(cnt[j] for j in xrange(i-l+1) if S[j:i]%D == 0) 77 | dp1[i%w] = addmod(dp1[i%w], prefix_total[suffix[i%w]]) # prefix_total[suffix[i]] = sum(dp1[j]+dp2[j] for j in xrange(i-l+1) if suffix[j] == suffix[i])%MOD 78 | dp2[i%w] = addmod(dp2[i%w], -prefix_dp1[suffix[i%w]]) # prefix_dp1[suffix[i]] = sum(dp1[j] for j in xrange(i-l+1) if suffix[j] == suffix[i])%MOD 79 | break 80 | if curr2 == 0 and suffix[j%w] == suffix[i%w]: # (S[j:i]%d_2_5 == 0) and (suffix[j]-suffix[i] == 0 <=> (S[j:i]*10^(len(S)-i)%d_remain == 0 and gcd(d_remain, 10^(len(S)-i)) == 1) <=> S[j:i]%d_remain == 0) <=> S[j:i]%D == 0 81 | dp1[i%w] = addmod(dp1[i%w], addmod(dp1[j%w], dp2[j%w])) 82 | dp2[i%w] = addmod(dp2[i%w], -dp1[j%w]) 83 | basis2 = basis2*10 % d_2_5 84 | accu_dp1 = addmod(accu_dp1, dp1[i%w]) 85 | return addmod(dp1[len(S)%w], dp2[len(S)%w]) 86 | 87 | MOD = 10**9+7 88 | for case in xrange(input()): 89 | print 'Case #%d: %s' % (case+1, divisible_divisions()) 90 | -------------------------------------------------------------------------------- /Round 3/fence_design.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Round 3 - Problem C. Fence Design 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000436142/0000000000813bc7 5 | # 6 | # Time: O(NlogN) on average, pass in PyPy2 but Python2 7 | # Space: O(N) 8 | # 9 | 10 | from random import seed, randint 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 | def same_side(A, B, C, D): 19 | return ccw(A,C,D) == 0 or ccw(B,C,D) == 0 or ccw(A,C,D) == ccw(B,C,D) 20 | 21 | def rotate(hull, split): 22 | for i in xrange(len(hull)): 23 | if hull[i] in split and hull[(i-1)%len(hull)] in split: 24 | return hull[i:]+hull[:i] 25 | return hull[:] 26 | 27 | def add_result(result, x): 28 | result.add(tuple(sorted(x))) 29 | 30 | def add_triangle(P, left_ccw, right_cw, result, lookup): 31 | p, q = 0, 1 32 | while True: 33 | p1 = (p+1)%len(left_ccw) 34 | if ccw(P[left_ccw[p1]], P[left_ccw[p]], P[right_cw[q]]) == CCW: 35 | add_result(result, [left_ccw[p1], right_cw[q]]) 36 | lookup.add(left_ccw[p]) # inside the convex hull 37 | p = p1 38 | continue 39 | q1 = (q+1)%len(right_cw) 40 | if ccw(P[left_ccw[p]], P[right_cw[q]], P[right_cw[q1]]) == CCW: 41 | add_result(result, [right_cw[q1], left_ccw[p]]) 42 | lookup.add(right_cw[q]) # inside the convex hull 43 | q = q1 44 | continue 45 | break 46 | 47 | def conquer(P, left, right, split, result): # Time: O(N) 48 | if len(left) == 2: 49 | return right 50 | if len(right) == 2: 51 | return left 52 | lookup = set() 53 | left_ccw, right_cw = rotate(left, split), rotate(right[::-1], split) 54 | add_triangle(P, left_ccw, right_cw, result, lookup) 55 | right_ccw, left_cw = rotate(right, split), rotate(left[::-1], split) 56 | add_triangle(P, right_ccw, left_cw, result, lookup) 57 | return [x for x in left_ccw if x not in lookup] + \ 58 | [x for x in right_ccw[1:-1] if x not in lookup] 59 | 60 | def divide(P, f, curr, split, result): # depth at most O(logN) on average => Time: O(NlogN) 61 | if len(curr) == 2: 62 | return curr 63 | if len(curr) == 3: # terminal case 64 | p = next(p for p in curr if p not in split) 65 | for x in split: 66 | add_result(result, [p, x]) 67 | return [p, split[0], split[1]] if ccw(P[p], P[split[0]], P[split[1]]) == CCW else [p, split[1], split[0]] 68 | if f: # prefer to use pre-placed fence 69 | new_split = f.pop() 70 | else: 71 | while True: 72 | idx = randint(0, len(curr)-1) 73 | p = curr[idx] 74 | curr[idx], curr[-1] = curr[-1], curr[idx] 75 | q = curr[randint(0, len(curr)-2)] 76 | if p > q: 77 | p, q = q, p 78 | if (p, q) not in result: 79 | break 80 | new_split = (p, q) 81 | add_result(result, new_split) 82 | left = [x for x in curr if ccw(P[new_split[0]], P[new_split[1]], P[x]) != CCW] 83 | right = [x for x in curr if ccw(P[new_split[0]], P[new_split[1]], P[x]) != CW] 84 | return conquer(P, 85 | divide(P, f if f and f[-1][0] in left and f[-1][1] in left else [], left, new_split, result), 86 | divide(P, f if f and f[-1][0] in right and f[-1][1] in right else [], right, new_split, result), 87 | new_split, result) 88 | 89 | def fence_design(): 90 | N = input() 91 | P = [map(int, raw_input().strip().split()) for _ in xrange(N)] 92 | f = [map(lambda x: int(x)-1, raw_input().strip().split()) for _ in xrange(2)] 93 | f = [tuple(sorted(x)) for x in f] 94 | 95 | if not same_side(P[f[0][0]], P[f[0][1]], P[f[1][0]], P[f[1][1]]): 96 | # make sure f[0] will be on the same side of f[1] 97 | f[0], f[1] = f[1], f[0] 98 | result = set() 99 | hull = divide(P, f[:], range(len(P)), [], result) 100 | assert(len(result) == 3*N-3-len(hull)) 101 | return "%s\n"%(len(result)-2)+"\n".join("%s %s"%(x[0]+1, x[1]+1) for x in [x for x in result if x not in f]) 102 | 103 | seed(0) 104 | for case in xrange(input()): 105 | print 'Case #%d: %s' % (case+1, fence_design()) 106 | -------------------------------------------------------------------------------- /Round 3/binary_search_game.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Round 3 - Problem D. Binary Search Game 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000436142/0000000000813e1b 5 | # 6 | # Time: O(N^2 + N * 2^(2^(L-1)) * 2^L), pass in PyPy2 but Python2 7 | # Space: O(N^2 + 2^L) 8 | # 9 | 10 | from collections import defaultdict, Counter 11 | 12 | def addmod(a, b): 13 | return (a+b)%MOD 14 | 15 | def mulmod(a, b): 16 | return (a*b)%MOD 17 | 18 | def inverse(n): # compute and cache, at most O(N) time and O(N) space in all test cases 19 | while len(inv) <= n: # lazy initialization 20 | inv.append(inv[MOD%len(inv)]*(MOD-MOD//len(inv)) % MOD) # https://cp-algorithms.com/algebra/module-inverse.html 21 | return inv[n] 22 | 23 | def inverse_factorial(n): # compute and cache, at most O(N) time and O(N) space in all test cases 24 | while len(inv_fact) <= n: # lazy initialization 25 | inv_fact.append(inv_fact[-1]*inverse(len(inv_fact)) % MOD) 26 | return inv_fact[n] 27 | 28 | def power(x, y): # compute and cache, at most O(N^2) time and O(N^2) space in each test case 29 | while len(POW[0][x]) <= y: # lazy initialization 30 | POW[0][x].append(mulmod(POW[0][x][-1], x)) 31 | return POW[0][x][y] 32 | 33 | # f(x) = c(N+1)*x^(N+1) + c(N)*x^N + ... + c(0)*x^0 34 | # given f(0), f(1), ... f(N+1), compute f(M) 35 | # usually, this should be done in O(N^2) time, 36 | # since x0, x1, ..., x(n-1) are consecutive integers, 37 | # we can compute in O(N) time 38 | def lagrange_interpolation(f, x): # Time: O(N) 39 | n = len(f) 40 | prefix = [1]*n 41 | for i in xrange(1, len(f)): # (x-x0)*(x-x1)* ... * (x-x(n-1)) 42 | prefix[i] = mulmod(prefix[i-1], x-(i-1)) 43 | suffix = [1]*n 44 | for i in reversed(xrange(len(f)-1)): 45 | suffix[i] = mulmod(suffix[i+1], x-(i+1)) 46 | result = 0 47 | for i in xrange(len(f)): 48 | a = mulmod(prefix[i], suffix[i]) # (x-x0)*(x-x1)* ... * (x-x(n-1)) / (x-xi) 49 | b = mulmod(inverse_factorial(i), mulmod((-1)**((n-1-i)%2), inverse_factorial(n-1-i))) # (1/i!) * ((-1)^((n-1-i)%2)/(n-1-i)!) 50 | result = addmod(result, mulmod(f[i], mulmod(a, b))) 51 | return result 52 | 53 | def mask_to_set(R, mask): # Time: O(N) 54 | result = set() 55 | for i in R: 56 | if mask&1: 57 | result.add(i) 58 | mask >>= 1 59 | return result 60 | 61 | # given chosen subset C from R where card values are all >= k, 62 | # count the number of ways to get final score >= k by considering the card values of U 63 | def count(N, M, L, A, U, C, k): # Time: O(2^L), Space: O(2^L) 64 | g = max(min(M-(k-1), M), 0) # number of choices greater or equal to k 65 | l = max(min(k-1, M), 0) # number of choices less than k 66 | # last decision done by whom would affect initial dp 67 | dp = [[l, g] if i in U else [0, 1] if i in C else [1, 0] for i in A] if L%2 else \ 68 | [[g, l] if i in U else [1, 0] if i in C else [0, 1] for i in A] 69 | while len(dp) != 1: 70 | dp = [[addmod(addmod(mulmod(dp[2*i][1], dp[2*i+1][1]), 71 | mulmod(dp[2*i][1], dp[2*i+1][0])), 72 | mulmod(dp[2*i][0], dp[2*i+1][1])), 73 | mulmod(dp[2*i][0], dp[2*i+1][0])] 74 | for i in xrange(len(dp)//2)] 75 | return mulmod(mulmod(dp[0][0], 76 | power(g, len(C))), 77 | power(l, N-len(U)-len(C))) 78 | 79 | def binary_search_game(): 80 | N, M, L = map(int, raw_input().strip().split()) 81 | A = map(lambda x: int(x)-1, raw_input().strip().split()) 82 | 83 | POW[0] = defaultdict(lambda:[1]) # cleanup global used cache to save space 84 | cnt = Counter(A) 85 | U, R = set(), set() # unique set and repeated set 86 | for i in A: 87 | if cnt[i] == 1: 88 | U.add(i) 89 | else: 90 | R.add(i) 91 | Z = set(i for i in xrange(N) if i not in U and i not in R) # unused set 92 | N -= len(Z) 93 | assert(len(R) <= len(A)//2) 94 | # f(x) which means the number of ways where the final scores is >= x, is a polynomial of x with at most N-degree 95 | # accumulated f(x) which means the sum of final scores with x^N different games, is a polynomial of x with at most (N+1)-degree by Faulhaber's formula, 96 | # and could be determinated by N+2 values of accumulated f(x) 97 | f = [0]*(min(N+1, M)+1) # if M < N+1, we can also just loop until f[M] is computed 98 | for mask in xrange(2**len(R)): # O(2^(2^(L-1))) times 99 | C = mask_to_set(R, mask) 100 | for k in xrange(1, len(f)): # O(N) times 101 | f[k] = addmod(f[k], count(N, M, L, A, U, C, k)) # Time: O(2^L) 102 | for k in xrange(1, len(f)): 103 | f[k] = addmod(f[k], f[k-1]) # accumulate f 104 | return mulmod(lagrange_interpolation(f, M), power(M, len(Z))) # Time: O(N) 105 | 106 | MOD = 10**9+7 107 | inv = [0, 1] 108 | inv_fact = [1, 1] 109 | POW = [None] 110 | for case in xrange(input()): 111 | print 'Case #%d: %s' % (case+1, binary_search_game()) 112 | -------------------------------------------------------------------------------- /Round 3/binary_search_game2.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Round 3 - Problem D. Binary Search Game 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000436142/0000000000813e1b 5 | # 6 | # Time: O(N * 2^(2^(L-1)) * 2^L), pass in PyPy2 but Python2 7 | # Space: O(N + L) 8 | # 9 | # optimized from binary_search_game.py, less time and space complexity, faster in power function, 10 | # but slower in recursive check function (iterative one is even slower since 2^L is small) compared to dp, 11 | # so overall runtime is slower 12 | # 13 | 14 | from collections import Counter 15 | 16 | def addmod(a, b): 17 | return (a+b)%MOD 18 | 19 | def mulmod(a, b): 20 | return (a*b)%MOD 21 | 22 | def inverse(n): # compute and cache, at most O(N) time and O(N) space in all test cases 23 | while len(inv) <= n: # lazy initialization 24 | inv.append(inv[MOD%len(inv)]*(MOD-MOD//len(inv)) % MOD) # https://cp-algorithms.com/algebra/module-inverse.html 25 | return inv[n] 26 | 27 | def inverse_factorial(n): # compute and cache, at most O(N) time and O(N) space in all test cases 28 | while len(inv_fact) <= n: # lazy initialization 29 | inv_fact.append(inv_fact[-1]*inverse(len(inv_fact)) % MOD) 30 | return inv_fact[n] 31 | 32 | # f(x) = c(N+1)*x^(N+1) + c(N)*x^N + ... + c(0)*x^0 33 | # given f(0), f(1), ... f(N+1), compute f(M) 34 | # usually, this should be done in O(N^2) time, 35 | # since x0, x1, ..., x(n-1) are consecutive integers, 36 | # we can compute in O(N) time 37 | def lagrange_interpolation(f, x): # Time: O(N) 38 | n = len(f) 39 | prefix = [1]*n 40 | for i in xrange(1, len(f)): # (x-x0)*(x-x1)* ... * (x-x(n-1)) 41 | prefix[i] = mulmod(prefix[i-1], x-(i-1)) 42 | suffix = [1]*n 43 | for i in reversed(xrange(len(f)-1)): 44 | suffix[i] = mulmod(suffix[i+1], x-(i+1)) 45 | result = 0 46 | for i in xrange(len(f)): 47 | a = mulmod(prefix[i], suffix[i]) # (x-x0)*(x-x1)* ... * (x-x(n-1)) / (x-xi) 48 | b = mulmod(inverse_factorial(i), mulmod((-1)**((n-1-i)%2), inverse_factorial(n-1-i))) # (1/i!) * ((-1)^((n-1-i)%2)/(n-1-i)!) 49 | result = addmod(result, mulmod(f[i], mulmod(a, b))) 50 | return result 51 | 52 | def mask_to_set(R, mask): # Time: O(N) 53 | result = set() 54 | for i in R: 55 | if mask&1: 56 | result.add(i) 57 | mask >>= 1 58 | return result 59 | 60 | def check(A, values, left, right): # Time: O(2^L), Space: O(L) 61 | if left == right: 62 | return values[A[left]] 63 | mid = left + (right-left)//2 64 | win1, lose1 = check(A, values, left, mid) 65 | win2, lose2 = check(A, values, mid+1, right) 66 | return [addmod(addmod(mulmod(lose1, lose2), 67 | mulmod(lose1, win2)), 68 | mulmod(win1, lose2)), 69 | mulmod(win1, win2)] 70 | 71 | # given chosen subset C from R where card values are all >= k, 72 | # count the number of ways to get final score >= k by considering the card values of U 73 | def count(N, M, L, A, U, R, C, k): # Time: O(2^L), Space: O(N + L) 74 | g = max(min(M-(k-1), M), 0) # number of choices greater or equal to k 75 | l = max(min(k-1, M), 0) # number of choices less than k 76 | # last decision done by whom would affect initial dp 77 | values = {i:[l, g] if i in U else [0, 1] if i in C else [1, 0] for i in set.union(U, R)} if L%2 else \ 78 | {i:[g, l] if i in U else [1, 0] if i in C else [0, 1] for i in set.union(U, R)} 79 | return mulmod(mulmod(check(A, values, 0, len(A)-1)[0], 80 | pow(g, len(C), MOD)), 81 | pow(l, N-len(U)-len(C), MOD)) # since pow(x, N, MOD) is O(logN), and N <= 32, we treat it as O(1) 82 | 83 | def binary_search_game(): 84 | N, M, L = map(int, raw_input().strip().split()) 85 | A = map(lambda x: int(x)-1, raw_input().strip().split()) 86 | 87 | cnt = Counter(A) 88 | U, R = set(), set() # unique set and repeated set 89 | for i in A: 90 | if cnt[i] == 1: 91 | U.add(i) 92 | else: 93 | R.add(i) 94 | Z = set(i for i in xrange(N) if i not in U and i not in R) # unused set 95 | N -= len(Z) 96 | assert(len(R) <= len(A)//2) 97 | # f(x) which means the number of ways where the final scores is >= x, is a polynomial of x with at most N-degree 98 | # accumulated f(x) which means the sum of final scores with x^N different games, is a polynomial of x with at most (N+1)-degree by Faulhaber's formula, 99 | # and could be determinated by N+2 values of accumulated f(x) 100 | f = [0]*(min(N+1, M)+1) # if M < N+1, we can also just loop until f[M] is computed 101 | for mask in xrange(2**len(R)): # O(2^(2^(L-1))) times 102 | C = mask_to_set(R, mask) 103 | for k in xrange(1, len(f)): # O(N) times 104 | f[k] = addmod(f[k], count(N, M, L, A, U, R, C, k)) # Time: O(2^L) 105 | for k in xrange(1, len(f)): 106 | f[k] = addmod(f[k], f[k-1]) # accumulate f 107 | return mulmod(lagrange_interpolation(f, M), pow(M, len(Z), MOD)) # Time: O(N), since pow(x, N, MOD) is O(logN), and N <= 32, we treat it as O(1) 108 | 109 | MOD = 10**9+7 110 | inv = [0, 1] 111 | inv_fact = [1, 1] 112 | for case in xrange(input()): 113 | print 'Case #%d: %s' % (case+1, binary_search_game()) 114 | -------------------------------------------------------------------------------- /Round 3/binary_search_game3.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Round 3 - Problem D. Binary Search Game 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000436142/0000000000813e1b 5 | # 6 | # Time: O(2^(2^(L-1)) * (2^L + N^2) + N^3) 7 | # Space: O(N^2 + L) 8 | # 9 | 10 | from collections import Counter 11 | 12 | def addmod(a, b): 13 | return (a+b)%MOD 14 | 15 | def submod(a, b): 16 | return (a-b)%MOD 17 | 18 | def mulmod(a, b): 19 | return (a*b)%MOD 20 | 21 | def inverse(n): # compute and cache, at most O(N) time and O(N) space in all test cases 22 | while len(inv) <= n: # lazy initialization 23 | inv.append(inv[MOD%len(inv)]*(MOD-MOD//len(inv)) % MOD) # https://cp-algorithms.com/algebra/module-inverse.html 24 | return inv[n] 25 | 26 | def inverse_factorial(n): # compute and cache, at most O(N) time and O(N) space in all test cases 27 | while len(inv_fact) <= n: # lazy initialization 28 | inv_fact.append(inv_fact[-1]*inverse(len(inv_fact)) % MOD) 29 | return inv_fact[n] 30 | 31 | # f(x) = c(N+1)*x^(N+1) + c(N)*x^N + ... + c(0)*x^0 32 | # given f(0), f(1), ... f(N+1), compute f(M) 33 | # usually, this should be done in O(N^2) time, 34 | # since x0, x1, ..., x(n-1) are consecutive integers, 35 | # we can compute in O(N) time 36 | def lagrange_interpolation(f, x): # Time: O(N) 37 | n = len(f) 38 | prefix = [1]*n 39 | for i in xrange(1, len(f)): # (x-x0)*(x-x1)* ... * (x-x(n-1)) 40 | prefix[i] = mulmod(prefix[i-1], x-(i-1)) 41 | suffix = [1]*n 42 | for i in reversed(xrange(len(f)-1)): 43 | suffix[i] = mulmod(suffix[i+1], x-(i+1)) 44 | result = 0 45 | for i in xrange(len(f)): 46 | a = mulmod(prefix[i], suffix[i]) # (x-x0)*(x-x1)* ... * (x-x(n-1)) / (x-xi) 47 | b = mulmod(inverse_factorial(i), mulmod((-1)**((n-1-i)%2), inverse_factorial(n-1-i))) # (1/i!) * ((-1)^((n-1-i)%2)/(n-1-i)!) 48 | result = addmod(result, mulmod(f[i], mulmod(a, b))) 49 | return result 50 | 51 | def mask_to_set(R, mask): # Time: O(N) 52 | result = set() 53 | for i in R: 54 | if mask&1: 55 | result.add(i) 56 | mask >>= 1 57 | return result 58 | 59 | def check(A, values, left, right): # Time: O(2^L), Space: O(L) 60 | if left == right: 61 | return values[A[left]] 62 | mid = left + (right-left)//2 63 | win1, lose1 = check(A, values, left, mid) 64 | win2, lose2 = check(A, values, mid+1, right) 65 | return [addmod(addmod(mulmod(lose1, lose2), 66 | mulmod(lose1, win2)), 67 | mulmod(win1, lose2)), 68 | mulmod(win1, win2)] 69 | 70 | # given f(x) = (M-(x-1))^a*(x-1)^b*M^(N-a-b), f(0) = 0 71 | # compute accumulated f(M) by Lagrange interpolation 72 | def g(N, M, a, b, lookup): # compute and cache, at most O(N^3) time and O(N^2) space in each test case 73 | if (a, b) not in lookup: # lazy initialization 74 | f = [mulmod(mulmod(pow(M-(k-1), a, MOD), pow(k-1, b, MOD)), pow(M, N-a-b, MOD)) if k else 0 for k in xrange(min(a+b+1, M)+1)] 75 | for k in xrange(1, len(f)): 76 | f[k] = addmod(f[k], f[k-1]) # accumulate f 77 | lookup[(a, b)] = lagrange_interpolation(f, M) 78 | return lookup[(a, b)] 79 | 80 | def binary_search_game(): 81 | N, M, L = map(int, raw_input().strip().split()) 82 | A = map(lambda x: int(x)-1, raw_input().strip().split()) 83 | 84 | left = {A[i] for i in xrange(len(A)//2)} # left half of board set 85 | right = {A[i] for i in xrange(len(A)//2, len(A))} # right half of board set 86 | both = left.intersection(right) 87 | left = {i for i in left if i not in both} 88 | right = {i for i in right if i not in both} 89 | default_state = [1, 0] if L%2 else [0, 1] 90 | chosen_state = default_state[::-1] 91 | result, values, lookup = 0, [default_state for _ in xrange(N)], {} 92 | for mask_both in xrange(2**len(both)): 93 | left_cnt, right_cnt = Counter(), Counter() 94 | both_C = mask_to_set(both, mask_both) 95 | for i in both: 96 | values[i] = chosen_state if i in both_C else default_state 97 | for mask_left in xrange(2**len(left)): # mask_left and mask_right are independent of states, loop O(2^(len(both)+len(left))) <= O(2^(2^L/2)) times 98 | left_C = mask_to_set(left, mask_left) 99 | for i in left: 100 | values[i] = chosen_state if i in left_C else default_state 101 | left_cnt[len(left_C)] += check(A, values, 0, (len(A)-1)//2)[0] # count if alice loses, Time: O(2^L) 102 | for mask_right in xrange(2**len(right)): # mask_left and mask_right are independent of states, loop O(2^(len(both)+len(right))) <= O(2^(2^L/2)) times 103 | right_C = mask_to_set(right, mask_right) 104 | for i in right: 105 | values[i] = chosen_state if i in right_C else default_state 106 | right_cnt[len(right_C)] += check(A, values, (len(A)-1)//2+1, len(A)-1)[0] # count if alice loses, Time: O(2^L) 107 | result = addmod(result, g(N, M, len(both_C), len(both)-len(both_C), lookup)) # add all count of both_C 108 | for i in xrange(len(left)+1): # O(N^2) times 109 | for j in xrange(len(right)+1): 110 | cnt = g(N, M, len(both_C)+i+j, (len(both)-len(both_C))+(len(left)-i)+(len(right)-j), lookup) 111 | result = submod(result, mulmod(mulmod(left_cnt[i], right_cnt[j]), cnt)) # sub lose count of both_C 112 | return result 113 | 114 | MOD = 10**9+7 115 | inv = [0, 1] 116 | inv_fact = [1, 1] 117 | for case in xrange(input()): 118 | print 'Case #%d: %s' % (case+1, binary_search_game()) 119 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [GoogleCodeJam 2021](https://codingcompetitions.withgoogle.com/codejam/archive/2021) ![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) ![Visitors](https://visitor-badge.laobi.icu/badge?page_id=kamyu104.googlecodejam.2021) 2 | 3 | * Python solutions of Google Code Jam 2021. Solution begins with `*` means it will get TLE in the largest data set. 4 | * Total computation amount > `10^8` is not friendly for Python to solve in 5 ~ 15 seconds. 5 | * A problem was marked as `Very Hard` means that it was an unsolved one during the contest and may not be that difficult. 6 | * From 2021-04, `PyPy3` is supported by the online judge. 7 | * From 2021-11, `Python2/PyPy2` is no longer supported by the online judge. 8 | * You need to run `2to3 -n -W --add-suffix=3 solution.py` to convert the solution into `Python3/PyPy3`. 9 | 10 | ## Rounds 11 | 12 | * [Code Jam 2020](https://github.com/kamyu104/GoogleCodeJam-2020) 13 | * [Qualification Round](https://github.com/kamyu104/GoogleCodeJam-2021#qualification-round) 14 | * [Round 1A](https://github.com/kamyu104/GoogleCodeJam-2021#round-1a) 15 | * [Round 1B](https://github.com/kamyu104/GoogleCodeJam-2021#round-1b) 16 | * [Round 1C](https://github.com/kamyu104/GoogleCodeJam-2021#round-1c) 17 | * [Round 2](https://github.com/kamyu104/GoogleCodeJam-2021#round-2) 18 | * [Round 3](https://github.com/kamyu104/GoogleCodeJam-2021#round-3) 19 | * [Virtual World Finals](https://github.com/kamyu104/GoogleCodeJam-2021#virtual-world-finals) 20 | * [Code Jam 2022](https://github.com/kamyu104/GoogleCodeJam-2022) 21 | 22 | ## Qualification Round 23 | | # | Title | Solution | Time | Space | Difficulty | Tag | Note | 24 | |---| ----- | -------- | ---- | ----- | ---------- | --- | ---- | 25 | |A| [Reversort](https://codingcompetitions.withgoogle.com/codejam/round/000000000043580a/00000000006d0a5c)| [Python](./Qualification%20Round/reversort.py)| _O(N^2)_ | _O(1)_ | Easy | | Simulation | 26 | |B| [Moons and Umbrellas](https://codingcompetitions.withgoogle.com/codejam/round/000000000043580a/00000000006d1145)| [Python](./Qualification%20Round/moons_and_umbrellas.py)| _O(N)_ | _O(1)_ | Easy | | DP | 27 | |C| [Reversort Engineering](https://codingcompetitions.withgoogle.com/codejam/round/000000000043580a/00000000006d12d7)| [Python](./Qualification%20Round/reversort_engineering.py) [Python](./Qualification%20Round/reversort_engineering2.py)| _O(N)_ | _O(1)_ | Easy | | Greedy | 28 | |D| [Median Sort](https://codingcompetitions.withgoogle.com/codejam/round/000000000043580a/00000000006d1284)| [Python](./Qualification%20Round/median_sort.py) | _O(N^2)_ | _O(1)_ | Medium | | Ternary Search, Insertion Sort | 29 | |E| [Cheating Detection](https://codingcompetitions.withgoogle.com/codejam/round/000000000043580a/00000000006d1155)| [Python](./Qualification%20Round/cheating_detection.py) [Python](./Qualification%20Round/cheating_detection2.py) [Python](./Qualification%20Round/cheating_detection3.py) [Python](./Qualification%20Round/cheating_detection4.py) | _O(S * Q)_ | _O(S + Q)_ | Hard | | Uniform Distribution, Inversions Counting, Correlation Coefficient | 30 | 31 | ## Round 1A 32 | | # | Title | Solution | Time | Space | Difficulty | Tag | Note | 33 | |---| ----- | -------- | ---- | ----- | ---------- | --- | ---- | 34 | |A| [Append Sort](https://codingcompetitions.withgoogle.com/codejam/round/000000000043585d/00000000007549e5)| [Python](./Round%201A/append_sort.py) [Python](./Round%201A/append_sort2.py) | _O(N * log(MAX_X))_ | _O(1)_ | Easy | | Greedy | 35 | |B| [Prime Time](https://codingcompetitions.withgoogle.com/codejam/round/000000000043585d/00000000007543d8)| [Python](./Round%201A/prime_time.py)| _O((MAX_P * logX) * (M + logX))_ | _O(1)_ | Medium | | Math, Prime Factorization, Pruning | 36 | |C| [Hacked Exam](https://codingcompetitions.withgoogle.com/codejam/round/000000000043585d/0000000000754750)| [Python](./Round%201A/hacked_exam.py) | precompute: _O(MAX_Q^2)_
runtime: _O(Q)_ | _O(MAX_Q^2)_ | Hard | | Binomial Coefficients, DP, Math, Expected Value | 37 | 38 | ## Round 1B 39 | | # | Title | Solution | Time | Space | Difficulty | Tag | Note | 40 | |---| ----- | -------- | ---- | ----- | ---------- | --- | ---- | 41 | |A| [Broken Clock](https://codingcompetitions.withgoogle.com/codejam/round/0000000000435baf/00000000007ae694)| [Python](./Round%201B/broken_clock.py) | _O(1)_ | _O(1)_ | Medium | | Math, Linear Congruence | 42 | |B| [Subtransmutation](https://codingcompetitions.withgoogle.com/codejam/round/0000000000435baf/00000000007ae4aa)| [Python](./Round%201B/subtransmutation.py) [Python](./Round%201B/subtransmutation2.py) | _O(MAX_M^2)_ | _O(MAX_M)_ | Medium | | Math, Bézout's Identity, Greedy | 43 | |C| [Digit Blocks](https://codingcompetitions.withgoogle.com/codejam/round/0000000000435baf/00000000007ae37b)| [Python](./Round%201B/digit_blocks.py) | precompute: _O(N^3 * B * D)_
runtime: _O(N * B)_ | _O(N^3 * B * D)_ | Hard | | DP, Math, Expected Value | 44 | 45 | ## Round 1C 46 | | # | Title | Solution | Time | Space | Difficulty | Tag | Note | 47 | |---| ----- | -------- | ---- | ----- | ---------- | --- | ---- | 48 | |A| [Closest Pick](https://codingcompetitions.withgoogle.com/codejam/round/00000000004362d7/00000000007c0f00)| [Python](./Round%201C/closest_pick.py) | _O(NlogN)_ | _O(N)_ | Easy | | Math, Sort | 49 | |B| [Roaring Years](https://codingcompetitions.withgoogle.com/codejam/round/00000000004362d7/00000000007c0f01)| [Python](./Round%201C/roaring_years.py) | _O(D^2 * logD)_ | _O(D)_ | Medium | | Math, Binary Search | 50 | |C| [Double or NOTing](https://codingcompetitions.withgoogle.com/codejam/round/00000000004362d7/00000000007c1139)| [Python](./Round%201C/double_or_noting.py) [Python](./Round%201C/double_or_noting2.py) [Python](./Round%201C/double_or_noting3.py) | _O(\|E\| + \|S\|)_ | _O(\|E\| + \|S\|)_ | Hard | | Math, Bit Manipulation, KMP Algorithm | 51 | 52 | ## Round 2 53 | | # | Title | Solution | Time | Space | Difficulty | Tag | Note | 54 | |---| ----- | -------- | ---- | ----- | ---------- | --- | ---- | 55 | |A| [Minimum Sort](https://codingcompetitions.withgoogle.com/codejam/round/0000000000435915/00000000007dc51c)| [Python](./Round%202/minimum_sort.py) | _O(ClogN)_ | _O(1)_ | Easy | | Selection Sort | 56 | |B| [Matrygons](https://codingcompetitions.withgoogle.com/codejam/round/0000000000435915/00000000007dbf06)| [Python](./Round%202/matrygons.py) | precompute: _O(NlogN)_
runtime: _O(1)_ | _O(N)_ | Medium | | Precompute, DP | 57 | |C| [Hidden Pancakes](https://codingcompetitions.withgoogle.com/codejam/round/0000000000435915/00000000007dc20c)| [Python](./Round%202/hidden_pancakes.py) [Python](./Round%202/hidden_pancakes2.py) | _O(N)_ | _O(N)_ | Medium | | Math, Binomial Coefficients, DP | 58 | |D| [Retiling](https://codingcompetitions.withgoogle.com/codejam/round/0000000000435915/00000000007dc2de)| [Python](./Round%202/retiling.py) | _O((R * C)^3)_ | _O((R * C)^2)_ | Hard | | Hungarian Weighted Bipartite Matching | 59 | 60 | ## Round 3 61 | | # | Title | Solution | Time | Space | Difficulty | Tag | Note | 62 | |---| ----- | -------- | ---- | ----- | ---------- | --- | ---- | 63 | |A| [Build-A-Pair](https://codingcompetitions.withgoogle.com/codejam/round/0000000000436142/0000000000813aa8)| [PyPy](./Round%203/build_a_pair.py) [PyPy](./Round%203/build_a_pair2.py) [Python](./Round%203/build_a_pair3.py) | _O(N)_ | _O(1)_ | Easy | | Enumeration, Greedy | 64 | |B| [Square Free](https://codingcompetitions.withgoogle.com/codejam/round/0000000000436142/0000000000813e1a)| [Python](./Round%203/square_free.py) | _O(R^2 * C^2)_ | _O(R + C)_ | Medium | | Max Flow, Greedy | 65 | |C| [Fence Design](https://codingcompetitions.withgoogle.com/codejam/round/0000000000436142/0000000000813bc7)| [PyPy](./Round%203/fence_design.py) | _O(NlogN)_ on average | _O(N)_ | Hard | ❤️ | Geometry, Convex Hull, Divide and Conquer, Random | 66 | |D| [Binary Search Game](https://codingcompetitions.withgoogle.com/codejam/round/0000000000436142/0000000000813e1b)| [PyPy](./Round%203/binary_search_game.py) [PyPy](./Round%203/binary_search_game2.py) [Python](./Round%203/binary_search_game3.py) | _O(2^(2^(L-1)) * (2^L + N^2) + N^3)_ | _O(N^2 + L)_ | Hard | ❤️ | Combinatorics, Counting, DP, Greedy, Polynomial, Faulhaber's Formula, Lagrange Interpolation | 67 | 68 | ## Virtual World Finals 69 | You can relive the magic of the 2021 Code Jam Virtual World Finals by watching the [Live Stream Recording](https://codingcompetitionsonair.withgoogle.com/events/cj-2021/watch?talk=cj-2021-livestream) of the competition, problem explanations, and announcement of winners. 70 | 71 | | # | Title | Solution | Time | Space | Difficulty | Tag | Note | 72 | |---| ----- | -------- | ---- | ----- | ---------- | --- | ---- | 73 | |A| [Cutting Cake](https://codingcompetitions.withgoogle.com/codejam/round/0000000000436329/000000000084fba1)| [Python](./Virtual%20World%20Finals/cutting_cake.py) | _O(NlogN)_ | _O(N)_ | Medium || Math, Line Sweep 74 | |B| [Slide Circuits](https://codingcompetitions.withgoogle.com/codejam/round/0000000000436329/000000000084f7b2)| [Python](./Virtual%20World%20Finals/slide_circuits.py) [Python](./Virtual%20World%20Finals/slide_circuits2.py) [Python](./Virtual%20World%20Finals/slide_circuits3.py) | _O(B + N + SlogS)_ | _O(SlogS)_ | Medium || Graph, Hash, Prefix Sum 75 | |C| [Ropes](https://codingcompetitions.withgoogle.com/codejam/round/0000000000436329/000000000084fad0)| [PyPy](./Virtual%20World%20Finals/ropes.py) | _O(N^3)_ | _O(N^2)_ | Hard | ❤️ | Greedy 76 | |D| [Divisible Divisions](https://codingcompetitions.withgoogle.com/codejam/round/0000000000436329/000000000084fb3a)| [Python](./Virtual%20World%20Finals/divisible_divisions.py) [Python](./Virtual%20World%20Finals/divisible_divisions2.py) | _O(\|S\|logD)_ | _O(min(\|S\|logD, D))_ | Medium || Math, Linear Congruence, Chinese Remainder Theorem, DP, Prefix Sum 77 | |E| [Infinitree](https://codingcompetitions.withgoogle.com/codejam/round/0000000000436329/000000000084fc01)| [PyPy](./Virtual%20World%20Finals/infinitree_concise.py) [PyPy3](./Virtual%20World%20Finals/infinitree_concise.py3) | _O(N^3.5 * logN + N^3 * logB)_ | _O(N^2.5 * logN + N^2 * logB)_ | Very Hard | ❤️ | Graph, Binary Tree, Matrix Exponentiation, Matrix Power Series, Prefix Sum, Binary Search, LCA, Tarjan's Algorithm 78 | -------------------------------------------------------------------------------- /Virtual World Finals/infinitree_concise.py3: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Virtual World Finals - Problem E. Infinitree 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000436329/000000000084fc01 5 | # 6 | # Time: O(N^3.5 * logN + N^3 * logB), pass in PyPy3 but Python3 7 | # Space: O(N^2.5 * logN + N^2 * logB) 8 | # 9 | # concise solution of infinitree.py 10 | # 11 | 12 | # Template modified from: 13 | # https://github.com/kamyu104/GoogleCodeJam-2017/blob/master/Round%202/beaming_with_joy.py 14 | def strongly_connected_components(graph): # Time: O(|V| + |E|) = O(N + 2N) = O(N), Space: O(|V|) = O(N) 15 | def strongconnect(v, index_counter, index, lowlinks, stack, stack_set, result): 16 | index[v] = index_counter[0] 17 | lowlinks[v] = index_counter[0] 18 | index_counter[0] += 1 19 | stack_set.add(v) 20 | stack.append(v) 21 | for w in (graph[v] if v in graph else []): 22 | if w not in index: 23 | strongconnect(w, index_counter, index, lowlinks, stack, stack_set, result) 24 | lowlinks[v] = min(lowlinks[v], lowlinks[w]) 25 | elif w in stack_set: 26 | lowlinks[v] = min(lowlinks[v], index[w]) 27 | if lowlinks[v] == index[v]: 28 | connected_component = [] 29 | w = None 30 | while w != v: 31 | w = stack.pop() 32 | stack_set.remove(w) 33 | connected_component.append(w) 34 | result.append(set(connected_component)) 35 | 36 | index_counter, index, lowlinks = [0], {}, {} 37 | stack, stack_set = [], set() 38 | result = [] 39 | strongconnect(ROOT_COLOR, index_counter, index, lowlinks, stack, stack_set, result) # modified, only care about reachable colors 40 | return result 41 | 42 | # return cycle_adj, cycle_length only if all reachable colors belong to at most one cycle 43 | def find_cycles(graph): # Time: O(N), Space: O(N) 44 | cycle_adj, cycle_length = {}, {} 45 | for scc in strongly_connected_components(graph): 46 | if any(sum(int(x in scc) for x in graph[node]) == 2 for node in scc): # this is optional optimization 47 | return {}, {} # have a reachable color belonging to more than one cycle, we only need to run single step solution 48 | if any(sum(int(x in scc) for x in graph[node]) != 1 for node in scc): 49 | continue 50 | node = next(iter(scc)) 51 | for _ in range(len(scc)): 52 | cycle_adj[node] = next(x for x in graph[node] if x in scc) 53 | cycle_length[node] = len(scc) 54 | node = cycle_adj[node] 55 | return cycle_adj, cycle_length 56 | 57 | def floor_log2_x(x): # Time: O(logx) 58 | return x.bit_length()-1 59 | 60 | def identity_matrix(N): # Time: O(N) 61 | return [[int(i == j) for j in range(N)] for i in range(N)] 62 | 63 | def e(i, N): # Time: O(N) 64 | return [int(j == i) for j in range(N)] 65 | 66 | def matrix_mult(A, B, INF): # Time: O(N^2) if A is a 1 x N matrix and B is a N x N matrix, O(N^3) if A, B are both N x N matrixs 67 | result = [[0]*len(B[0]) for _ in range(len(A))] 68 | B_T = list(zip(*B)) 69 | for result_i, A_i in zip(result, A): 70 | for j, B_T_i in enumerate(B_T): 71 | for A_i_j, B_T_i_j in zip(A_i, B_T_i): 72 | result_i[j] += A_i_j*B_T_i_j 73 | if result_i[j] > INF: 74 | result_i[j] = INF 75 | break 76 | return result 77 | 78 | def matrix_add(A, B, INF): # Time: O(N) if A, B are both 1 x N matrixs, O(N^2) if A, B are both N x N matrixs 79 | result = [[0]*len(B[0]) for _ in range(len(A))] 80 | for result_i, A_i, B_i in zip(result, A, B): 81 | for j in range(len(result_i)): 82 | result_i[j] = A_i[j]+B_i[j] 83 | if result_i[j] > INF: 84 | result_i[j] = INF 85 | return result 86 | 87 | # build [M, M^2, ..., M^(2^logx)] and [I, (I + M), (I + M + M^2 + M^3), (I + M + ... + M^(2^logx-1))] 88 | def build_powers_and_power_series(N, M, INF, x): # Time: O(N^3 * logx) 89 | logx = floor_log2_x(x) 90 | I = identity_matrix(N) 91 | # M_powers[i] for i in range(1+logx): 92 | # 0: M 93 | # 1: M^2 94 | # ... 95 | # logx: M^(2^logx) 96 | M_powers = [M] 97 | for _ in range(logx): # Time: O(N^3 * logx) 98 | M_powers.append(matrix_mult(M_powers[-1], M_powers[-1], INF)) 99 | # M_power_series[i] for i in range(1+logx): 100 | # 0: I 101 | # 1: (I + M) * I = I + M 102 | # 2: (I + M^2) * (I + M) = I + M + M^2 + M^3 103 | # ... 104 | # logx: (I + M^(2^(logx-1))) * (I + M + ... + M^(2^(logx-1)-1)) = I + M + ... + M^(2^logx-1) 105 | M_power_series = [I] 106 | for i in range(logx): # Time: O(N^3 * logx) 107 | matrix = matrix_add(I, M_powers[i], INF) 108 | M_power_series.append(matrix_mult(matrix, M_power_series[-1], INF)) 109 | return M_powers, M_power_series 110 | 111 | # V * M^x by vector-matrix or matrix-matrix exponentiation 112 | def get_V_M_power_x(M_powers, INF, V, x): # Time: O(N^2 * logx) if V is a 1 x N matrix, O(N^3 * logx) if V is a N x N matrix 113 | basis, i = 1, 0 114 | while basis <= x: 115 | if x&basis: 116 | V = matrix_mult(V, M_powers[i], INF) 117 | basis, i = basis<<1, i+1 118 | return V 119 | 120 | # v * (I + M + M^2 + ... + M^x) by vector-matrix exponentiation 121 | def get_v_M_power_series_x(N, M_powers, M_power_series, INF, v, x): # Time: O(N^2 * logx) 122 | x += 1 123 | u = [0]*N 124 | basis, i = 1, 0 125 | while basis <= x: 126 | if x&basis: 127 | # new_Pr = P_r*M^(2^i) + P_(2^i) 128 | # new_u = v * new_Pr = v * (P_r*M^(2^i) + P_(2^i)) = u*M^(2^i) + v*P_(2^i) 129 | u = matrix_add(matrix_mult([u], M_powers[i], INF), 130 | matrix_mult([v], M_power_series[i], INF), 131 | INF)[0] 132 | basis, i = basis<<1, i+1 133 | return u 134 | 135 | def get_depth(N, M_powers, M_power_series, INF, v, x): # Time: O(N^2 * logx) 136 | logx = floor_log2_x(x) 137 | result = 0 138 | u = [0]*N 139 | basis = 1<>= 1 151 | return result 152 | 153 | def get_single_step_position(M_powers, INF, ec, h, x): # Time: O(N^2 * logx) 154 | left_cnt = sum(get_V_M_power_x(M_powers, INF, [ec], h-1)[0]) 155 | return (LEFT, x) if x < left_cnt else (RIGHT, x-left_cnt) 156 | 157 | def get_multiple_steps_position(M_powers, Mh_power_series, INF, logp, v, delta_h, ec, x): # Time: O(N^2 * log(delta_h)) 158 | left_cnt = sum(get_V_M_power_x(M_powers, INF, matrix_mult([v], Mh_power_series[logp], INF), delta_h)[0]) 159 | mid_cnt = sum(get_V_M_power_x(M_powers, INF, [ec], delta_h)[0]) 160 | return 0 <= x-left_cnt < mid_cnt, x-left_cnt 161 | 162 | def infinitree(): 163 | N, A, B = list(map(int, input().strip().split())) 164 | L = list(map(int, input().strip().split())) 165 | R = list(map(int, input().strip().split())) 166 | 167 | N += 1 168 | if A > B: 169 | A, B, = B, A 170 | INF = B 171 | M = [[0]*N for _ in range(N)] 172 | graph = {LEAF_COLOR:[]} 173 | for i in range(ROOT_COLOR, N): 174 | M[i][L[i-1]] += 1 175 | M[i][R[i-1]] += 1 176 | graph[i] = [L[i-1], R[i-1]] 177 | 178 | Mh_powers, Mh_power_series = {}, {} 179 | Mh_powers[1], Mh_power_series[1] = build_powers_and_power_series(N, M, INF, B) # Time: O(N^3 * logB) 180 | h1 = get_depth(N, Mh_powers[1], Mh_power_series[1], INF, e(ROOT_COLOR, N), A) 181 | h2 = get_depth(N, Mh_powers[1], Mh_power_series[1], INF, e(ROOT_COLOR, N), B) 182 | x1 = A-sum(get_v_M_power_series_x(N, Mh_powers[1], Mh_power_series[1], INF, e(ROOT_COLOR, N), h1-1))-1 183 | x2 = B-sum(get_v_M_power_series_x(N, Mh_powers[1], Mh_power_series[1], INF, e(ROOT_COLOR, N), h2-1))-1 184 | cycle_adj, cycle_length = find_cycles(graph) 185 | c = ROOT_COLOR 186 | while (h1, x1) != (0, 0): 187 | if c not in cycle_adj: # enter none-only-1-cycle node, Time: O(N^2 * logB) => Total Time: O(N^2 * (logB)^2) 188 | side1, new_x1 = get_single_step_position(Mh_powers[1], INF, e(L[c-1], N), h1, x1) 189 | side2, new_x2 = get_single_step_position(Mh_powers[1], INF, e(L[c-1], N), h2, x2) 190 | if side1 != side2: # found lca 191 | break 192 | h1, x1 = h1-1, new_x1 193 | h2, x2 = h2-1, new_x2 194 | c = (L[c-1] if side1 == LEFT else R[c-1]) 195 | continue 196 | # path from root to lca enter a new unseen cycle, we can speed up in this part of path, 197 | # we run this multiple steps solution only if all reachable colors belong to at most one cycle, 198 | # otherwise, the binary tree grows exponentially with height at most O(logB) so that single step solution is fast enough, 199 | # and also saves the extra time and space cost from multiple steps solution 200 | h = cycle_length[c] 201 | if h not in Mh_powers: # lazy init, sum(distinct h) = N => distinct h at most O(sqrt(N)) times, each Time: O(N^3 * logh + N^3 * log(hi)) => Total Time: O(N^3.5 * logN + (N^3.5 * log(logB) + N^3 * logB)) = O(N^3.5 * logN + N^3 * logB) assumed O(N) = O(logB) 202 | Mh_powers[h], Mh_power_series[h] = build_powers_and_power_series(N, get_V_M_power_x(Mh_powers[1], INF, identity_matrix(N), h), INF, min(h1, h2)) 203 | cycle, v = [], [0]*N 204 | for x in reversed(range(h)): # Time: O(h * N^2 * logN) => Total Time O(N^3 * logN) 205 | if cycle_adj[c] == R[c-1]: 206 | v = matrix_add([v], [get_V_M_power_x(Mh_powers[1], INF, [e(L[c-1], N)], x)[0]], INF)[0] 207 | c = cycle_adj[c] 208 | cycle.append(c) 209 | p, logp = 1, 0 210 | while (p*2)*h <= min(h1, h2): 211 | p, logp = p*2, logp+1 212 | while p: # logp times => Total Time: O(k cycles * logp times * (N^2 * log(delta_h))) = O(N^3 * log(logB)^2 + N^2 * (logB)^2) = O(N^3 * logB) assumed O(N) = O(logB) 213 | if p*h <= min(h1, h2): 214 | ok1, new_x1 = get_multiple_steps_position(Mh_powers[1], Mh_power_series[h], INF, logp, v, h1-p*h, e(c, N), x1) 215 | ok2, new_x2 = get_multiple_steps_position(Mh_powers[1], Mh_power_series[h], INF, logp, v, h2-p*h, e(c, N), x2) 216 | if ok1 and ok2: 217 | h1, x1 = h1-p*h, new_x1 218 | h2, x2 = h2-p*h, new_x2 219 | p, logp = p//2, logp-1 220 | for x in cycle: # no need to do multiple steps solution 221 | del cycle_adj[x] 222 | return h1+h2 223 | 224 | LEFT, RIGHT = range(2) 225 | LEAF_COLOR, ROOT_COLOR = range(2) # leaf color is 0, root color is 1 226 | for case in range(int(input())): 227 | print('Case #%d: %s' % (case+1, infinitree())) 228 | -------------------------------------------------------------------------------- /Virtual World Finals/infinitree_concise.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Virtual World Finals - Problem E. Infinitree 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000436329/000000000084fc01 5 | # 6 | # Time: O(N^3.5 * logN + N^3 * logB), pass in PyPy2 but Python2 7 | # Space: O(N^2.5 * logN + N^2 * logB) 8 | # 9 | # concise solution of infinitree.py 10 | # 11 | 12 | from itertools import izip 13 | 14 | # Template modified from: 15 | # https://github.com/kamyu104/GoogleCodeJam-2017/blob/master/Round%202/beaming_with_joy.py 16 | def strongly_connected_components(graph): # Time: O(|V| + |E|) = O(N + 2N) = O(N), Space: O(|V|) = O(N) 17 | def strongconnect(v, index_counter, index, lowlinks, stack, stack_set, result): 18 | index[v] = index_counter[0] 19 | lowlinks[v] = index_counter[0] 20 | index_counter[0] += 1 21 | stack_set.add(v) 22 | stack.append(v) 23 | for w in (graph[v] if v in graph else []): 24 | if w not in index: 25 | strongconnect(w, index_counter, index, lowlinks, stack, stack_set, result) 26 | lowlinks[v] = min(lowlinks[v], lowlinks[w]) 27 | elif w in stack_set: 28 | lowlinks[v] = min(lowlinks[v], index[w]) 29 | if lowlinks[v] == index[v]: 30 | connected_component = [] 31 | w = None 32 | while w != v: 33 | w = stack.pop() 34 | stack_set.remove(w) 35 | connected_component.append(w) 36 | result.append(set(connected_component)) 37 | 38 | index_counter, index, lowlinks = [0], {}, {} 39 | stack, stack_set = [], set() 40 | result = [] 41 | strongconnect(ROOT_COLOR, index_counter, index, lowlinks, stack, stack_set, result) # modified, only care about reachable colors 42 | return result 43 | 44 | # return cycle_adj, cycle_length only if all reachable colors belong to at most one cycle 45 | def find_cycles(graph): # Time: O(N), Space: O(N) 46 | cycle_adj, cycle_length = {}, {} 47 | for scc in strongly_connected_components(graph): 48 | if any(sum(int(x in scc) for x in graph[node]) == 2 for node in scc): # this is optional optimization 49 | return {}, {} # have a reachable color belonging to more than one cycle, we only need to run single step solution 50 | if any(sum(int(x in scc) for x in graph[node]) != 1 for node in scc): 51 | continue 52 | node = next(iter(scc)) 53 | for _ in xrange(len(scc)): 54 | cycle_adj[node] = next(x for x in graph[node] if x in scc) 55 | cycle_length[node] = len(scc) 56 | node = cycle_adj[node] 57 | return cycle_adj, cycle_length 58 | 59 | def floor_log2_x(x): # Time: O(logx) 60 | return x.bit_length()-1 61 | 62 | def identity_matrix(N): # Time: O(N) 63 | return [[int(i == j) for j in xrange(N)] for i in xrange(N)] 64 | 65 | def e(i, N): # Time: O(N) 66 | return [int(j == i) for j in xrange(N)] 67 | 68 | def matrix_mult(A, B, INF): # Time: O(N^2) if A is a 1 x N matrix and B is a N x N matrix, O(N^3) if A, B are both N x N matrixs 69 | result = [[0]*len(B[0]) for _ in xrange(len(A))] 70 | B_T = zip(*B) 71 | for result_i, A_i in izip(result, A): 72 | for j, B_T_i in enumerate(B_T): 73 | for A_i_j, B_T_i_j in izip(A_i, B_T_i): 74 | result_i[j] += A_i_j*B_T_i_j 75 | if result_i[j] > INF: 76 | result_i[j] = INF 77 | break 78 | return result 79 | 80 | def matrix_add(A, B, INF): # Time: O(N) if A, B are both 1 x N matrixs, O(N^2) if A, B are both N x N matrixs 81 | result = [[0]*len(B[0]) for _ in xrange(len(A))] 82 | for result_i, A_i, B_i in izip(result, A, B): 83 | for j in xrange(len(result_i)): 84 | result_i[j] = A_i[j]+B_i[j] 85 | if result_i[j] > INF: 86 | result_i[j] = INF 87 | return result 88 | 89 | # build [M, M^2, ..., M^(2^logx)] and [I, (I + M), (I + M + M^2 + M^3), (I + M + ... + M^(2^logx-1))] 90 | def build_powers_and_power_series(N, M, INF, x): # Time: O(N^3 * logx) 91 | logx = floor_log2_x(x) 92 | I = identity_matrix(N) 93 | # M_powers[i] for i in xrange(1+logx): 94 | # 0: M 95 | # 1: M^2 96 | # ... 97 | # logx: M^(2^logx) 98 | M_powers = [M] 99 | for _ in xrange(logx): # Time: O(N^3 * logx) 100 | M_powers.append(matrix_mult(M_powers[-1], M_powers[-1], INF)) 101 | # M_power_series[i] for i in xrange(1+logx): 102 | # 0: I 103 | # 1: (I + M) * I = I + M 104 | # 2: (I + M^2) * (I + M) = I + M + M^2 + M^3 105 | # ... 106 | # logx: (I + M^(2^(logx-1))) * (I + M + ... + M^(2^(logx-1)-1)) = I + M + ... + M^(2^logx-1) 107 | M_power_series = [I] 108 | for i in xrange(logx): # Time: O(N^3 * logx) 109 | matrix = matrix_add(I, M_powers[i], INF) 110 | M_power_series.append(matrix_mult(matrix, M_power_series[-1], INF)) 111 | return M_powers, M_power_series 112 | 113 | # V * M^x by vector-matrix or matrix-matrix exponentiation 114 | def get_V_M_power_x(M_powers, INF, V, x): # Time: O(N^2 * logx) if V is a 1 x N matrix, O(N^3 * logx) if V is a N x N matrix 115 | basis, i = 1, 0 116 | while basis <= x: 117 | if x&basis: 118 | V = matrix_mult(V, M_powers[i], INF) 119 | basis, i = basis<<1, i+1 120 | return V 121 | 122 | # v * (I + M + M^2 + ... + M^x) by vector-matrix exponentiation 123 | def get_v_M_power_series_x(N, M_powers, M_power_series, INF, v, x): # Time: O(N^2 * logx) 124 | x += 1 125 | u = [0]*N 126 | basis, i = 1, 0 127 | while basis <= x: 128 | if x&basis: 129 | # new_Pr = P_r*M^(2^i) + P_(2^i) 130 | # new_u = v * new_Pr = v * (P_r*M^(2^i) + P_(2^i)) = u*M^(2^i) + v*P_(2^i) 131 | u = matrix_add(matrix_mult([u], M_powers[i], INF), 132 | matrix_mult([v], M_power_series[i], INF), 133 | INF)[0] 134 | basis, i = basis<<1, i+1 135 | return u 136 | 137 | def get_depth(N, M_powers, M_power_series, INF, v, x): # Time: O(N^2 * logx) 138 | logx = floor_log2_x(x) 139 | result = 0 140 | u = [0]*N 141 | basis = 1<>= 1 153 | return result 154 | 155 | def get_single_step_position(M_powers, INF, ec, h, x): # Time: O(N^2 * logx) 156 | left_cnt = sum(get_V_M_power_x(M_powers, INF, [ec], h-1)[0]) 157 | return (LEFT, x) if x < left_cnt else (RIGHT, x-left_cnt) 158 | 159 | def get_multiple_steps_position(M_powers, Mh_power_series, INF, logp, v, delta_h, ec, x): # Time: O(N^2 * log(delta_h)) 160 | left_cnt = sum(get_V_M_power_x(M_powers, INF, matrix_mult([v], Mh_power_series[logp], INF), delta_h)[0]) 161 | mid_cnt = sum(get_V_M_power_x(M_powers, INF, [ec], delta_h)[0]) 162 | return 0 <= x-left_cnt < mid_cnt, x-left_cnt 163 | 164 | def infinitree(): 165 | N, A, B = map(int, raw_input().strip().split()) 166 | L = map(int, raw_input().strip().split()) 167 | R = map(int, raw_input().strip().split()) 168 | 169 | N += 1 170 | if A > B: 171 | A, B, = B, A 172 | INF = B 173 | M = [[0]*N for _ in xrange(N)] 174 | graph = {LEAF_COLOR:[]} 175 | for i in xrange(ROOT_COLOR, N): 176 | M[i][L[i-1]] += 1 177 | M[i][R[i-1]] += 1 178 | graph[i] = [L[i-1], R[i-1]] 179 | 180 | Mh_powers, Mh_power_series = {}, {} 181 | Mh_powers[1], Mh_power_series[1] = build_powers_and_power_series(N, M, INF, B) # Time: O(N^3 * logB) 182 | h1 = get_depth(N, Mh_powers[1], Mh_power_series[1], INF, e(ROOT_COLOR, N), A) 183 | h2 = get_depth(N, Mh_powers[1], Mh_power_series[1], INF, e(ROOT_COLOR, N), B) 184 | x1 = A-sum(get_v_M_power_series_x(N, Mh_powers[1], Mh_power_series[1], INF, e(ROOT_COLOR, N), h1-1))-1 185 | x2 = B-sum(get_v_M_power_series_x(N, Mh_powers[1], Mh_power_series[1], INF, e(ROOT_COLOR, N), h2-1))-1 186 | cycle_adj, cycle_length = find_cycles(graph) 187 | c = ROOT_COLOR 188 | while (h1, x1) != (0, 0): 189 | if c not in cycle_adj: # enter none-only-1-cycle node, Time: O(N^2 * logB) => Total Time: O(N^2 * (logB)^2) 190 | side1, new_x1 = get_single_step_position(Mh_powers[1], INF, e(L[c-1], N), h1, x1) 191 | side2, new_x2 = get_single_step_position(Mh_powers[1], INF, e(L[c-1], N), h2, x2) 192 | if side1 != side2: # found lca 193 | break 194 | h1, x1 = h1-1, new_x1 195 | h2, x2 = h2-1, new_x2 196 | c = (L[c-1] if side1 == LEFT else R[c-1]) 197 | continue 198 | # path from root to lca enter a new unseen cycle, we can speed up in this part of path, 199 | # we run this multiple steps solution only if all reachable colors belong to at most one cycle, 200 | # otherwise, the binary tree grows exponentially with height at most O(logB) so that single step solution is fast enough, 201 | # and also saves the extra time and space cost from multiple steps solution 202 | h = cycle_length[c] 203 | if h not in Mh_powers: # lazy init, sum(distinct h) = N => distinct h at most O(sqrt(N)) times, each Time: O(N^3 * logh + N^3 * log(hi)) => Total Time: O(N^3.5 * logN + (N^3.5 * log(logB) + N^3 * logB)) = O(N^3.5 * logN + N^3 * logB) assumed O(N) = O(logB) 204 | Mh_powers[h], Mh_power_series[h] = build_powers_and_power_series(N, get_V_M_power_x(Mh_powers[1], INF, identity_matrix(N), h), INF, min(h1, h2)) 205 | cycle, v = [], [0]*N 206 | for x in reversed(xrange(h)): # Time: O(h * N^2 * logN) => Total Time O(N^3 * logN) 207 | if cycle_adj[c] == R[c-1]: 208 | v = matrix_add([v], [get_V_M_power_x(Mh_powers[1], INF, [e(L[c-1], N)], x)[0]], INF)[0] 209 | c = cycle_adj[c] 210 | cycle.append(c) 211 | p, logp = 1, 0 212 | while (p*2)*h <= min(h1, h2): 213 | p, logp = p*2, logp+1 214 | while p: # logp times => Total Time: O(k cycles * logp times * (N^2 * log(delta_h))) = O(N^3 * log(logB)^2 + N^2 * (logB)^2) = O(N^3 * logB) assumed O(N) = O(logB) 215 | if p*h <= min(h1, h2): 216 | ok1, new_x1 = get_multiple_steps_position(Mh_powers[1], Mh_power_series[h], INF, logp, v, h1-p*h, e(c, N), x1) 217 | ok2, new_x2 = get_multiple_steps_position(Mh_powers[1], Mh_power_series[h], INF, logp, v, h2-p*h, e(c, N), x2) 218 | if ok1 and ok2: 219 | h1, x1 = h1-p*h, new_x1 220 | h2, x2 = h2-p*h, new_x2 221 | p, logp = p//2, logp-1 222 | for x in cycle: # no need to do multiple steps solution 223 | del cycle_adj[x] 224 | return h1+h2 225 | 226 | LEFT, RIGHT = xrange(2) 227 | LEAF_COLOR, ROOT_COLOR = xrange(2) # leaf color is 0, root color is 1 228 | for case in xrange(input()): 229 | print 'Case #%d: %s' % (case+1, infinitree()) 230 | -------------------------------------------------------------------------------- /Virtual World Finals/infinitree.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021 kamyu. All rights reserved. 2 | # 3 | # Google Code Jam 2021 Virtual World Finals - Problem E. Infinitree 4 | # https://codingcompetitions.withgoogle.com/codejam/round/0000000000436329/000000000084fc01 5 | # 6 | # Time: O(N^3.5 * logN + N^3 * logB), pass in PyPy2 but Python2 7 | # Space: O(N^2.5 * logN + N^2 * logB) 8 | # 9 | 10 | from itertools import izip 11 | 12 | # Template modified from: 13 | # https://github.com/kamyu104/GoogleCodeJam-2017/blob/master/Round%202/beaming_with_joy.py 14 | def strongly_connected_components(graph): # Time: O(|V| + |E|) = O(N + 2N) = O(N), Space: O(|V|) = O(N) 15 | def strongconnect(v, index_counter, index, lowlinks, stack, stack_set, result): 16 | index[v] = index_counter[0] 17 | lowlinks[v] = index_counter[0] 18 | index_counter[0] += 1 19 | stack_set.add(v) 20 | stack.append(v) 21 | for w in (graph[v] if v in graph else []): 22 | if w not in index: 23 | strongconnect(w, index_counter, index, lowlinks, stack, stack_set, result) 24 | lowlinks[v] = min(lowlinks[v], lowlinks[w]) 25 | elif w in stack_set: 26 | lowlinks[v] = min(lowlinks[v], index[w]) 27 | if lowlinks[v] == index[v]: 28 | connected_component = [] 29 | w = None 30 | while w != v: 31 | w = stack.pop() 32 | stack_set.remove(w) 33 | connected_component.append(w) 34 | result.append(set(connected_component)) 35 | 36 | index_counter, index, lowlinks = [0], {}, {} 37 | stack, stack_set = [], set() 38 | result = [] 39 | strongconnect(ROOT_COLOR, index_counter, index, lowlinks, stack, stack_set, result) # modified, only care about reachable colors 40 | return result 41 | 42 | # return cycle_adj, cycle_length only if all reachable colors belong to at most one cycle 43 | def find_cycles(graph): # Time: O(N), Space: O(N) 44 | cycle_adj, cycle_length = {}, {} 45 | for scc in strongly_connected_components(graph): 46 | if any(sum(int(x in scc) for x in graph[node]) == 2 for node in scc): # this is optional optimization 47 | return {}, {} # have a reachable color belonging to more than one cycle, we only need to run single step solution 48 | if any(sum(int(x in scc) for x in graph[node]) != 1 for node in scc): 49 | continue 50 | node = next(iter(scc)) 51 | for _ in xrange(len(scc)): 52 | cycle_adj[node] = next(x for x in graph[node] if x in scc) 53 | cycle_length[node] = len(scc) 54 | node = cycle_adj[node] 55 | return cycle_adj, cycle_length 56 | 57 | def floor_log2_x(x): # Time: O(logx) 58 | return x.bit_length()-1 59 | 60 | def identity_matrix(N): # Time: O(N) 61 | return [[int(i == j) for j in xrange(N)] for i in xrange(N)] 62 | 63 | def e(i, N): # Time: O(N) 64 | return [int(j == i) for j in xrange(N)] 65 | 66 | def vector_mult(A, B, INF): # Time: O(N^2), A is a N-d vector, B is a N x N matrix 67 | result = [0]*len(B[0]) 68 | B_T = zip(*B) 69 | for i, B_T_i in enumerate(B_T): 70 | for _, (A_j, B_T_i_j) in enumerate(izip(A, B_T_i)): 71 | result[i] += A_j*B_T_i_j 72 | if result[i] > INF: 73 | result[i] = INF 74 | break 75 | return result 76 | 77 | def matrix_mult(A, B, INF): # Time: O(N^3), A, B are both N x N matrixs 78 | result = [[0]*len(B[0]) for _ in xrange(len(A))] 79 | B_T = zip(*B) 80 | for result_i, A_i in izip(result, A): 81 | for j, B_T_i in enumerate(B_T): 82 | for A_i_j, B_T_i_j in izip(A_i, B_T_i): 83 | result_i[j] += A_i_j*B_T_i_j 84 | if result_i[j] > INF: 85 | result_i[j] = INF 86 | break 87 | return result 88 | 89 | def vector_add(A, B, INF): # Time: O(N) 90 | result = [0]*len(A) 91 | for i, (A_i, B_i) in enumerate(izip(A, B)): 92 | result[i] = A_i+B_i 93 | if result[i] > INF: 94 | result[i] = INF 95 | return result 96 | 97 | def matrix_add(A, B, INF): # Time: O(N^2) 98 | result = [[0]*len(B[0]) for _ in xrange(len(A))] 99 | for result_i, A_i, B_i in izip(result, A, B): 100 | for j in xrange(len(result_i)): 101 | result_i[j] = A_i[j]+B_i[j] 102 | if result_i[j] > INF: 103 | result_i[j] = INF 104 | return result 105 | 106 | # build [M, M^2, ..., M^(2^logx)] and [I, (I + M), (I + M + M^2 + M^3), (I + M + ... + M^(2^logx-1))] 107 | def build_powers_and_power_series(N, M, INF, x): # Time: O(N^3 * logx) 108 | logx = floor_log2_x(x) 109 | I = identity_matrix(N) 110 | # M_powers[i] for i in xrange(1+logx): 111 | # 0: M 112 | # 1: M^2 113 | # ... 114 | # logx: M^(2^logx) 115 | M_powers = [M] 116 | for _ in xrange(logx): # Time: O(N^3 * logx) 117 | M_powers.append(matrix_mult(M_powers[-1], M_powers[-1], INF)) 118 | # M_power_series[i] for i in xrange(1+logx): 119 | # 0: I 120 | # 1: (I + M) * I = I + M 121 | # 2: (I + M^2) * (I + M) = I + M + M^2 + M^3 122 | # ... 123 | # logx: (I + M^(2^(logx-1))) * (I + M + ... + M^(2^(logx-1)-1)) = I + M + ... + M^(2^logx-1) 124 | M_power_series = [I] 125 | for i in xrange(logx): # Time: O(N^3 * logx) 126 | matrix = matrix_add(I, M_powers[i], INF) 127 | M_power_series.append(matrix_mult(matrix, M_power_series[-1], INF)) 128 | return M_powers, M_power_series 129 | 130 | # V * M^x by matrix exponentiation 131 | def get_V_M_power_x(M_powers, INF, V, x): # Time: O(N^3 * logx) 132 | basis, i = 1, 0 133 | while basis <= x: 134 | if x&basis: 135 | V = matrix_mult(V, M_powers[i], INF) 136 | basis, i = basis<<1, i+1 137 | return V 138 | 139 | # v * M^x by vector-matrix exponentiation 140 | def get_v_M_power_x(M_powers, INF, v, x): # Time: O(N^2 * logx) 141 | basis, i = 1, 0 142 | while basis <= x: 143 | if x&basis: 144 | v = vector_mult(v, M_powers[i], INF) 145 | basis, i = basis<<1, i+1 146 | return v 147 | 148 | # v * (I + M + M^2 + ... + M^x) by vector-matrix exponentiation 149 | def get_v_M_power_series_x(N, M_powers, M_power_series, INF, v, x): # Time: O(N^2 * logx) 150 | x += 1 151 | u = [0]*N 152 | basis, i = 1, 0 153 | while basis <= x: 154 | if x&basis: 155 | # new_Pr = P_r*M^(2^i) + P_(2^i) 156 | # new_u = v * new_Pr = v * (P_r*M^(2^i) + P_(2^i)) = u*M^(2^i) + v*P_(2^i) 157 | u = vector_add(vector_mult(u, M_powers[i], INF), 158 | vector_mult(v, M_power_series[i], INF), 159 | INF) 160 | basis, i = basis<<1, i+1 161 | return u 162 | 163 | def get_depth(N, M_powers, M_power_series, INF, v, x): # Time: O(N^2 * logx) 164 | logx = floor_log2_x(x) 165 | result = 0 166 | u = [0]*N 167 | basis = 1<>= 1 179 | return result 180 | 181 | def get_single_step_position(M_powers, INF, ec, h, x): # Time: O(N^2 * logx) 182 | left_cnt = sum(get_v_M_power_x(M_powers, INF, ec, h-1)) 183 | return (LEFT, x) if x < left_cnt else (RIGHT, x-left_cnt) 184 | 185 | def get_multiple_steps_position(M_powers, Mh_power_series, INF, logp, v, delta_h, ec, x): # Time: O(N^2 * log(delta_h)) 186 | left_cnt = sum(get_v_M_power_x(M_powers, INF, vector_mult(v, Mh_power_series[logp], INF), delta_h)) 187 | mid_cnt = sum(get_v_M_power_x(M_powers, INF, ec, delta_h)) 188 | return 0 <= x-left_cnt < mid_cnt, x-left_cnt 189 | 190 | def infinitree(): 191 | N, A, B = map(int, raw_input().strip().split()) 192 | L = map(int, raw_input().strip().split()) 193 | R = map(int, raw_input().strip().split()) 194 | 195 | N += 1 196 | if A > B: 197 | A, B, = B, A 198 | INF = B 199 | M = [[0]*N for _ in xrange(N)] 200 | graph = {LEAF_COLOR:[]} 201 | for i in xrange(ROOT_COLOR, N): 202 | M[i][L[i-1]] += 1 203 | M[i][R[i-1]] += 1 204 | graph[i] = [L[i-1], R[i-1]] 205 | 206 | Mh_powers, Mh_power_series = {}, {} 207 | Mh_powers[1], Mh_power_series[1] = build_powers_and_power_series(N, M, INF, B) # Time: O(N^3 * logB) 208 | h1 = get_depth(N, Mh_powers[1], Mh_power_series[1], INF, e(ROOT_COLOR, N), A) 209 | h2 = get_depth(N, Mh_powers[1], Mh_power_series[1], INF, e(ROOT_COLOR, N), B) 210 | x1 = A-sum(get_v_M_power_series_x(N, Mh_powers[1], Mh_power_series[1], INF, e(ROOT_COLOR, N), h1-1))-1 211 | x2 = B-sum(get_v_M_power_series_x(N, Mh_powers[1], Mh_power_series[1], INF, e(ROOT_COLOR, N), h2-1))-1 212 | cycle_adj, cycle_length = find_cycles(graph) 213 | c = ROOT_COLOR 214 | while (h1, x1) != (0, 0): 215 | if c not in cycle_adj: # enter none-only-1-cycle node, Time: O(N^2 * logB) => Total Time: O(N^2 * (logB)^2) 216 | side1, new_x1 = get_single_step_position(Mh_powers[1], INF, e(L[c-1], N), h1, x1) 217 | side2, new_x2 = get_single_step_position(Mh_powers[1], INF, e(L[c-1], N), h2, x2) 218 | if side1 != side2: # found lca 219 | break 220 | h1, x1 = h1-1, new_x1 221 | h2, x2 = h2-1, new_x2 222 | c = (L[c-1] if side1 == LEFT else R[c-1]) 223 | continue 224 | # path from root to lca enter a new unseen cycle, we can speed up in this part of path, 225 | # we run this multiple steps solution only if all reachable colors belong to at most one cycle, 226 | # otherwise, the binary tree grows exponentially with height at most O(logB) so that single step solution is fast enough, 227 | # and also saves the extra time and space cost from multiple steps solution 228 | h = cycle_length[c] 229 | if h not in Mh_powers: # lazy init, sum(distinct h) = N => distinct h at most O(sqrt(N)) times, each Time: O(N^3 * logh + N^3 * log(hi)) => Total Time: O(N^3.5 * logN + (N^3.5 * log(logB) + N^3 * logB)) = O(N^3.5 * logN + N^3 * logB) assumed O(N) = O(logB) 230 | Mh_powers[h], Mh_power_series[h] = build_powers_and_power_series(N, get_V_M_power_x(Mh_powers[1], INF, identity_matrix(N), h), INF, min(h1, h2)) 231 | cycle, v = [], [0]*N 232 | for x in reversed(xrange(h)): # Time: O(h * N^2 * logN) => Total Time O(N^3 * logN) 233 | if cycle_adj[c] == R[c-1]: 234 | v = vector_add(v, get_v_M_power_x(Mh_powers[1], INF, e(L[c-1], N), x), INF) 235 | c = cycle_adj[c] 236 | cycle.append(c) 237 | p, logp = 1, 0 238 | while (p*2)*h <= min(h1, h2): 239 | p, logp = p*2, logp+1 240 | while p: # logp times => Total Time: O(k cycles * logp times * (N^2 * log(delta_h))) = O(N^3 * log(logB)^2 + N^2 * (logB)^2) = O(N^3 * logB) assumed O(N) = O(logB) 241 | if p*h <= min(h1, h2): 242 | ok1, new_x1 = get_multiple_steps_position(Mh_powers[1], Mh_power_series[h], INF, logp, v, h1-p*h, e(c, N), x1) 243 | ok2, new_x2 = get_multiple_steps_position(Mh_powers[1], Mh_power_series[h], INF, logp, v, h2-p*h, e(c, N), x2) 244 | if ok1 and ok2: 245 | h1, x1 = h1-p*h, new_x1 246 | h2, x2 = h2-p*h, new_x2 247 | p, logp = p//2, logp-1 248 | for x in cycle: # no need to do multiple steps solution 249 | del cycle_adj[x] 250 | return h1+h2 251 | 252 | LEFT, RIGHT = xrange(2) 253 | LEAF_COLOR, ROOT_COLOR = xrange(2) # leaf color is 0, root color is 1 254 | for case in xrange(input()): 255 | print 'Case #%d: %s' % (case+1, infinitree()) 256 | --------------------------------------------------------------------------------