|
1 | 1 | from aoc import *
|
2 |
| -from statistics import median, mode |
3 |
| -from more_itertools import windowed, take, peekable, chunked, first_true, split_when, first_true |
4 |
| -from heapq import * |
5 |
| -# from functools import reduce, cache, cmp_to_key |
6 | 2 | from functools import cache
|
7 |
| -from math import ceil |
8 |
| -import re |
9 |
| -from blessed import BlessedList |
10 |
| -from copy import deepcopy |
11 |
| -import operator |
12 |
| -from string import ascii_letters, ascii_lowercase, ascii_uppercase, digits, hexdigits, whitespace |
13 |
| -from pprint import pprint as pp |
14 |
| -import bisect |
15 |
| -import math |
16 |
| -from itertools import pairwise, combinations |
17 |
| -import sys |
18 |
| -from collections import defaultdict, OrderedDict, deque, Counter |
19 |
| -# from ordered_set import OrderedSet |
| 3 | +from collections import Counter |
| 4 | + |
20 | 5 |
|
21 | 6 | S = [(-1, 0), (1, 0), (0, -1), (0, 1)]
|
22 | 7 |
|
23 | 8 |
|
24 | 9 | def main(input_file: str):
|
25 | 10 | m = load_map_dd(input_file)
|
26 | 11 | maxx = max(x for x, y in m)
|
27 |
| - sizex = maxx + 1 |
28 |
| - sizey = maxy + 1 |
| 12 | + sizex = maxx + 1 # 131 |
29 | 13 | maxy = max(y for x, y in m)
|
30 |
| - # pp(m) |
| 14 | + sizey = maxy + 1 # 131 |
| 15 | + |
31 | 16 | @cache
|
32 | 17 | def step(x, y):
|
33 |
| - return {((x + dx), |
34 |
| - (y + dy)) for dx, dy in S |
35 |
| - if m[((x + dx) % (maxx + 1), |
36 |
| - (y + dy) % (maxy + 1))] in ['.', 'S']} |
| 18 | + return { |
| 19 | + ((x + dx), (y + dy)) |
| 20 | + for dx, dy in S |
| 21 | + if m[((x + dx) % (maxx + 1), (y + dy) % (maxy + 1))] in ['.', 'S'] |
| 22 | + } |
37 | 23 |
|
38 | 24 | places = {(x, y) for (x, y), c in m.items() if c == 'S'}
|
39 | 25 | c = Counter()
|
40 | 26 | c[next(iter(places))] = 1
|
| 27 | + start = next(iter(places)) |
| 28 | + m[start] = '.' |
41 | 29 | steps = 26501365
|
42 |
| - size = steps * 2 + 1 |
43 | 30 | suma = 0
|
44 |
| - for i in range(size): |
45 |
| - if i <= size // 2: |
46 |
| - # we are above or in the middle of the rhombus, the width is |
47 |
| - # increasing |
48 |
| - row = i % sizex |
49 |
| - width = i * 2 + 1 |
50 |
| - if width <= sizey: |
51 |
| - # take inner size of the rhomboid |
52 |
| - for col in range(sizey // 2 - i, width, 2): |
53 |
| - if m[i, col] == '.': |
54 |
| - suma += 1 |
55 |
| - else: |
56 |
| - # take outer size of the rhomboid |
57 |
| - full_widths = width // sizey |
58 |
| - remainder = width % sizey |
59 |
| - if remainder <= sizey // 2: |
60 |
| - # Count the edges |
61 |
| - # XXX.........XXX |
62 |
| - pass |
63 |
| - |
64 |
| - else: |
65 |
| - # Count the inside |
66 |
| - # XXXXXX... |
67 |
| - # ...XXXXXX |
68 |
| - # ^^^......counting just these |
69 |
| - pass |
70 |
| - pass |
71 |
| - else: |
72 |
| - # we are below rhombus, the width is decreasing |
73 |
| - pass |
74 |
| - for i in range(50): |
75 |
| - new_places = set() |
76 |
| - new_counter = Counter() |
77 |
| - for x, y in places: |
78 |
| - # steps_number = c[x, y] |
79 |
| - # c[x, y] = 0 |
80 |
| - new_steps = step(x, y) |
81 |
| - new_places.update(new_steps) |
82 |
| - # for nx, ny in new_steps: |
83 |
| - # new_counter[nx, ny] = steps_number |
84 |
| - places = new_places |
85 |
| - c = new_counter |
86 |
| - # pp(m) |
87 |
| - # for ri in range(maxx + 1): |
88 |
| - # for ci in range(maxy + 1): |
89 |
| - # if (ri, ci) in places: |
90 |
| - # print('O', end='') |
91 |
| - # else: |
92 |
| - # print(m[(ri, ci)], end='') |
93 |
| - # print() |
94 |
| - # pp(c) |
95 |
| - return sum(c.values()) |
| 31 | + |
| 32 | + # The best solution is finding the quadratic recurence. Since I can't find |
| 33 | + # it on my own, I have painstakingly split the whole farm into 14 types of |
| 34 | + # squares (each square represents visited places in one map): |
| 35 | + # |
| 36 | + # ^ - UP pentagon |
| 37 | + # V - DOWN pentagon |
| 38 | + # < - LEFT pentagon |
| 39 | + # > - RIGHT pentagon |
| 40 | + # / - RIGHT-DOWN pentagon |
| 41 | + # \ - LEFT-DOWN pentagon |
| 42 | + # ( - LEFT-UP pentagon |
| 43 | + # ) - RIGHT-UP pentagon |
| 44 | + # , - RIGHT-DOWN triangle |
| 45 | + # . - LEFT-DOWN triangle |
| 46 | + # ` - RIGHT-UP triangle |
| 47 | + # ' - LEFT-UP triangle |
| 48 | + # e - even parity square |
| 49 | + # o - odd parity square |
| 50 | + # S - starting square (the same as even parity square) |
| 51 | + # |
| 52 | + # ,^. |
| 53 | + # ,/e\. |
| 54 | + # ,/eoe\. |
| 55 | + # ,/eoeoe\. |
| 56 | + # ,/eoeoeoe\. |
| 57 | + # <eoeoSoeoe> |
| 58 | + # `(eoeoeoe)' |
| 59 | + # `(eoeoe)' |
| 60 | + # `(eoe)' |
| 61 | + # `(e)' |
| 62 | + # `V' |
| 63 | + # |
| 64 | + # Pentagons have odd parity an triangles have even parity. |
| 65 | + # |
| 66 | + # Parity can be computed by adding two coordinates together and computing |
| 67 | + # modulo two. (0, 0) has even parity, (1, 2) has odd parity. |
| 68 | + # |
| 69 | + # The input is constructed so that the walking stops right at the edges of |
| 70 | + # squares (0, 65) for the up pentagon, (130, 65) for the down pentagon, (65, |
| 71 | + # 0) for the left pentagon, (65, 130) for the right pentagon. It's because |
| 72 | + # the middle row and middle columns are garden plots. There's also garden |
| 73 | + # plots inside the square creating a 45-degree rotated square of garden |
| 74 | + # plots. Since there are no rocks, this creates a perfect sharp line of |
| 75 | + # visited garden plots in pentagons and triangles, not requiring to manually |
| 76 | + # do flood fill or search. |
| 77 | + # |
| 78 | + # Thus the problem reduces to just counting the number of each square in the |
| 79 | + # whole farm and then counting the garden plots with the correct parity. |
| 80 | + # |
| 81 | + # And one special thing with this approach -- there are some garden plots |
| 82 | + # not visitable by moving in four directions. I have manually detected these |
| 83 | + # using my eyes and a bucket tool in Gimp. The adjusted input file is |
| 84 | + # attached in the repository. |
| 85 | + |
| 86 | + # Compute all types of squares |
| 87 | + up_pentagon = 0 |
| 88 | + for i in range(sizex // 2): |
| 89 | + for col in range(sizey // 2 - i, sizey // 2 + i + 1): |
| 90 | + if (i + col) % 2 and m[i, col] == '.': |
| 91 | + up_pentagon += 1 |
| 92 | + for i in range(sizex // 2, sizex): |
| 93 | + for col in range(sizey): |
| 94 | + if (i + col) % 2 and m[i, col] == '.': |
| 95 | + up_pentagon += 1 |
| 96 | + |
| 97 | + down_pentagon = 0 |
| 98 | + for i in range(sizex // 2): |
| 99 | + for col in range(sizey // 2 - i, sizey // 2 + i + 1): |
| 100 | + if (maxx - i + col) % 2 and m[maxx - i, col] == '.': |
| 101 | + down_pentagon += 1 |
| 102 | + for i in range(sizex // 2, sizex): |
| 103 | + for col in range(sizey): |
| 104 | + if (maxx - i + col) % 2 and m[maxx - i, col] == '.': |
| 105 | + down_pentagon += 1 |
| 106 | + |
| 107 | + right_pentagon = 0 |
| 108 | + for i in range(sizex // 2): |
| 109 | + for col in range(sizey // 2 - i, sizey // 2 + i + 1): |
| 110 | + if (maxx - i + col) % 2 and m[col, maxx - i] == '.': |
| 111 | + right_pentagon += 1 |
| 112 | + for i in range(sizex // 2, sizex): |
| 113 | + for col in range(sizey): |
| 114 | + if (maxx - i + col) % 2 and m[col, maxx - i] == '.': |
| 115 | + right_pentagon += 1 |
| 116 | + |
| 117 | + left_pentagon = 0 |
| 118 | + for i in range(sizex // 2): |
| 119 | + for col in range(sizey // 2 - i, sizey // 2 + i + 1): |
| 120 | + if (i + col) % 2 and m[col, i] == '.': |
| 121 | + left_pentagon += 1 |
| 122 | + for i in range(sizex // 2, sizex): |
| 123 | + for col in range(sizey): |
| 124 | + if (i + col) % 2 and m[col, i] == '.': |
| 125 | + left_pentagon += 1 |
| 126 | + |
| 127 | + right_down_pentagon = 0 |
| 128 | + for i in range(sizex // 2): |
| 129 | + for col in range(sizey // 2 - i, sizex): |
| 130 | + if (i + col) % 2 and m[i, col] == '.': |
| 131 | + right_down_pentagon += 1 |
| 132 | + for i in range(sizex // 2, sizex): |
| 133 | + for col in range(sizey): |
| 134 | + if (i + col) % 2 and m[i, col] == '.': |
| 135 | + right_down_pentagon += 1 |
| 136 | + |
| 137 | + left_down_pentagon = 0 |
| 138 | + for i in range(sizex // 2): |
| 139 | + for col in range(sizey // 2 + i + 1): |
| 140 | + if (i + col) % 2 and m[i, col] == '.': |
| 141 | + left_down_pentagon += 1 |
| 142 | + for i in range(sizex // 2, sizex): |
| 143 | + for col in range(sizey): |
| 144 | + if (i + col) % 2 and m[i, col] == '.': |
| 145 | + left_down_pentagon += 1 |
| 146 | + |
| 147 | + right_up_pentagon = 0 |
| 148 | + for i in range(sizex // 2): |
| 149 | + for col in range(sizey // 2 - i, sizex): |
| 150 | + if (maxx - i + col) % 2 and m[maxx - i, col] == '.': |
| 151 | + right_up_pentagon += 1 |
| 152 | + for i in range(sizex // 2, sizex): |
| 153 | + for col in range(sizey): |
| 154 | + if (maxx - i + col) % 2 and m[maxx - i, col] == '.': |
| 155 | + right_up_pentagon += 1 |
| 156 | + |
| 157 | + left_up_pentagon = 0 |
| 158 | + for i in range(sizex // 2): |
| 159 | + for col in range(sizey // 2 + i + 1): |
| 160 | + if (maxx - i + col) % 2 and m[maxx - i, col] == '.': |
| 161 | + left_up_pentagon += 1 |
| 162 | + for i in range(sizex // 2, sizex): |
| 163 | + for col in range(sizey): |
| 164 | + if (maxx - i + col) % 2 and m[maxx - i, col] == '.': |
| 165 | + left_up_pentagon += 1 |
| 166 | + |
| 167 | + left_up_triangle = 0 |
| 168 | + for i in range(sizex // 2): |
| 169 | + for col in range(sizey // 2 - i): |
| 170 | + # The last one should be (64, 0) |
| 171 | + if (i + col) % 2 == 0 and m[i, col] == '.': |
| 172 | + left_up_triangle += 1 |
| 173 | + |
| 174 | + left_down_triangle = 0 |
| 175 | + for i in range(sizex // 2): |
| 176 | + for col in range(sizey // 2 - i): |
| 177 | + if (maxx - i + col) % 2 == 0 and m[maxx - i, col] == '.': |
| 178 | + left_down_triangle += 1 |
| 179 | + |
| 180 | + right_up_triangle = 0 |
| 181 | + for i in range(sizex // 2): |
| 182 | + for col in range(sizey // 2 + 1 + i, sizey): |
| 183 | + # The last one should be (64, 0) |
| 184 | + if (i + col) % 2 == 0 and m[i, col] == '.': |
| 185 | + right_up_triangle += 1 |
| 186 | + |
| 187 | + right_down_triangle = 0 |
| 188 | + for i in range(sizex // 2): |
| 189 | + for col in range(sizey // 2 + 1 + i, sizey): |
| 190 | + if (maxx - i + col) % 2 == 0 and m[maxx - i, col] == '.': |
| 191 | + right_down_triangle += 1 |
| 192 | + |
| 193 | + even_square = 0 |
| 194 | + for i in range(sizex): |
| 195 | + for col in range(sizey): |
| 196 | + if (i + col) % 2 == 0 and m[i, col] == '.': |
| 197 | + even_square += 1 |
| 198 | + |
| 199 | + odd_square = 0 |
| 200 | + for i in range(sizex): |
| 201 | + for col in range(sizey): |
| 202 | + if (i + col) % 2 and m[i, col] == '.': |
| 203 | + odd_square += 1 |
| 204 | + |
| 205 | + steps = (steps - sizex // 2) // sizex |
| 206 | + suma += ( |
| 207 | + up_pentagon |
| 208 | + + down_pentagon |
| 209 | + + left_pentagon |
| 210 | + + right_pentagon |
| 211 | + + (steps - 1) |
| 212 | + * ( |
| 213 | + right_down_pentagon |
| 214 | + + right_up_pentagon |
| 215 | + + left_down_pentagon |
| 216 | + + left_up_pentagon |
| 217 | + + right_down_triangle |
| 218 | + + right_up_triangle |
| 219 | + + left_down_triangle |
| 220 | + + left_up_triangle |
| 221 | + ) |
| 222 | + + right_down_triangle |
| 223 | + + right_up_triangle |
| 224 | + + left_down_triangle |
| 225 | + + left_up_triangle |
| 226 | + + steps * (steps + 1) // 2 * even_square |
| 227 | + + steps * (steps - 1) // 2 * odd_square |
| 228 | + + steps * (steps - 1) // 2 * even_square |
| 229 | + + (steps - 1) * (steps - 2) // 2 * odd_square |
| 230 | + ) |
| 231 | + return suma |
| 232 | + |
96 | 233 |
|
97 | 234 | DAY = 21
|
98 |
| -FILE_TEST = f"{DAY}_test.txt" |
99 |
| -FILE_EXP = f"{DAY}_expa.txt" |
100 |
| -FILE = f"{DAY}.txt" |
101 |
| -# test_and_submit(main, FILE_TEST, FILE_EXP, FILE, DAY) |
102 |
| -print(main(FILE_TEST)) |
103 |
| -# print(main(FILE)) |
| 235 | +FILE = f"{DAY}mod.txt" |
| 236 | +print(main(FILE)) |
0 commit comments