diff --git a/extra/distract-the-trainers/solution.py b/extra/distract-the-trainers/solution.py index 356cf68..4bcd847 100644 --- a/extra/distract-the-trainers/solution.py +++ b/extra/distract-the-trainers/solution.py @@ -1,48 +1,78 @@ def solution(banana_list): - fewest_possible_number = len(banana_list) - memo = {} - for pairs in generate_trainer_pairs(list(range(len(banana_list)))): - possible_number = len(banana_list) - for i, j in pairs: - if check_loop(banana_list[i], banana_list[j], memo): - possible_number -= 2 - if possible_number < fewest_possible_number: - fewest_possible_number = possible_number - if fewest_possible_number < 2: # early termination - break - return fewest_possible_number + loop_memo = {} + edge_map = {i: [] for i in range(len(banana_list))} + + for i in range(len(banana_list)): + for j in range(i+1, len(banana_list)): + if check_loop(banana_list[i], banana_list[j], loop_memo): + edge_map[i] += [j] + edge_map[j] += [i] + + return len(banana_list) - len(find_maximum_matching(edge_map)) + +def find_maximum_matching(edge_map): + exposed_vertex_list = [i for i in edge_map] + matching = {} + while len(exposed_vertex_list) >= 2: + start = exposed_vertex_list.pop() + path, end = find_new_path(edge_map, start, exposed_vertex_list, matching, {}) + if end in exposed_vertex_list: + update_matching(matching, start, path, end) + exposed_vertex_list.remove(end) + return matching import copy -def generate_trainer_pairs(trainer_id_list): - if len(trainer_id_list) < 2: - yield [] - else: - first_id = trainer_id_list[0] - for second_id in trainer_id_list[1:]: - reduced_list = copy.deepcopy(trainer_id_list) - seduced_list.remove(first_id) - reduced_list.remove(second_id) - for pairs in generate_trainer_pairs(reduced_list): - yield [(first_id, second_id)] + pairs +def find_new_path(edge_map, curr, end_list, matching, color): + for neighbor in edge_map[curr]: + if neighbor in end_list: + return [], neighbor + color[curr] = True + for neighbor in edge_map[curr]: + if neighbor in color: + continue + temp_color = copy.deepcopy(color) + temp_color[neighbor] = True + new_path, end = find_new_path(edge_map, matching[neighbor], end_list, matching, temp_color) + return [(curr, neighbor)] + new_path, end + return [], None + +def update_matching(matching, start, path, end): + new_left = start + for left, right in path: + matching[new_left] = left + matching[left] = new_left + new_left = right + matching[new_left] = end + matching[end] = new_left + +# How check_loop works +# 1. (ac, bc) => (2ac, bc-ac) = (a, b) => (2a, b-a) +# * a < b +# 1. (ac, bc+d) => (2ac, bc+d-ac) = (2ac, (b-a)c+d) +# * a < b and 0 < d < c +# 1. (a, 2n-a) => (2a, 2n-2a) = (a, n-a) +# * a < n +# 1. therefore, (x, y) will loop iff (x + y) % 2 == 1 +# * x and y are relative prime number +def check_loop(x, y, memo): + if (x, y) in memo: + return memo[(x, y)] + return bin((x + y)/gcd(x, y)).count('1') != 1 + +def gcd(m, n): + while n != 0: + t = m % n + m, n = n, t + return m -def check_loop(a, b, memo): - if (a, b) in memo: - return memo[(a, b)] - while a != b: - if (a + b) % 2 == 1: # sum is odd - memo[(a, b)] = True - return True - if a > b: - a, b = (a - b)/2, b - else: - a, b = a, (b - a)/2 - memo[(a, b)] = False - return False tests = [ ([1], 1), ([1, 1], 2), ([1, 7, 3, 21, 13, 19], 0), + #([1 for i in range(100)], 100), + #([2**(i+1) - 1 for i in range(10)], 2), + #([2**(i+1) - 1 for i in range(20)], 2), ] for i, o in tests: