@@ -1,33 +1,45 @@ | |||
import functools | |||
import itertools | |||
import collections | |||
import re | |||
text = open(0).read() | |||
grid = {} | |||
match = {} | |||
for x, y, a, b in [[int(n) for n in re.findall(r'-?\d+', ln)] for ln in text.splitlines()]: | |||
match[complex(x, y)] = complex(a, b) | |||
ns = [int(n) for n in ''.join(n if n.isdigit() else ' ' for n in text).split()] | |||
circles = {(x, y, abs(x - a) + abs(y - b)) for x, y , a, b in zip(*[iter(ns)] * 4)} | |||
goal = 2000000 | |||
bop = 4000000 | |||
seen = set() | |||
count = collections.Counter() | |||
for aa, bb in match.items(): | |||
diff = bb - aa | |||
reach = int(abs(diff.real) + abs(diff.imag)) | |||
dist = int(abs(aa.imag - goal)) | |||
if reach > dist: | |||
df = reach - dist | |||
seen |= set(range(int(aa.real - df), int(aa.real + df))) | |||
for rot in [1j ** i for i in range(4)]: | |||
for dx in range(reach): | |||
dy = reach - dx | |||
edgy = aa + complex(dx, dy) * rot | |||
for dot in [rot, rot * 1j]: | |||
bip = edgy + dot | |||
if 0 <= bip.real <= bop and 0 <= bip.imag <= bop: | |||
count[bip] += 1 | |||
y_lim = 2_000_000 | |||
seen = set() | |||
for x, y, r in circles: | |||
dy = abs(y_lim - y) | |||
dx = r - dy | |||
seen |= set(range(x - dx, x + dx)) | |||
print(len(seen)) | |||
fin = count.most_common()[0][0] | |||
print(fin, int(fin.real * bop + fin.imag)) | |||
criss = [] | |||
cross = [] | |||
for x, y, r in circles: | |||
p = complex(x, y) | |||
for i in range(4): | |||
line = (p + r * 1j ** i), (1j ** (i + 1) + 1j ** (i + 2) ), r + 1 | |||
if i % 2: | |||
criss.append(line) | |||
else: | |||
cross.append(line) | |||
def intersection(l1, l2): | |||
(p1, d1, r1), (p2, d2, r2) = l1, l2 | |||
for x in range(r1 + 1): | |||
# (p1 + d1 * x) = (p2 + d2 * ?) | |||
if (p1 + d1 * x) - p2 == d2: | |||
if ((p1 + d1 * x - p2) / d2).real <= r2: | |||
yield p1 + d1 * x | |||
counter = collections.Counter() | |||
for l1, l2 in itertools.product(criss, cross): | |||
for p in intersection(l1, l2): | |||
counter[p] += 1 | |||
print(max(counter, key=counter.get)) |
@@ -0,0 +1,61 @@ | |||
import collections | |||
import itertools | |||
import re | |||
text = open(0).read() | |||
graph = collections.defaultdict(dict) | |||
rates = {} | |||
for ln in text.splitlines(): | |||
source, rate, *leads_to = re.findall(r'[A-Z]{2}|\d+', ln) | |||
for out in leads_to: | |||
graph[out][source] = 1 | |||
graph[source][out] = 1 | |||
rates[source] = int(rate) | |||
costs = {} | |||
for k in graph: | |||
edge = set(graph[k]) | |||
seen = set() | |||
cost = 1 | |||
while True: | |||
for e in edge: | |||
if (k, e) in costs: | |||
costs[k, e] = min(costs[k, e], cost) | |||
else: | |||
costs[k, e] = cost | |||
cost += 1 | |||
edge = {n for e in edge for n in graph[e]} - seen | |||
if seen == set(graph): | |||
break | |||
seen |= edge | |||
def for_one(combo, lim): | |||
tt = 0 | |||
bob = [0] | |||
for a, b in zip(('AA',) + combo, combo): | |||
tt = costs[a, b] | |||
for _ in range(tt): | |||
bob.append(bob[-1]) | |||
bob.append(bob[-1] + rates[b]) | |||
pending = max(0, lim - len(bob)) | |||
bob += [bob[-1]] * pending | |||
return sum(bob[:lim]) | |||
def generate_combos(pending, left, carry=('AA',)): | |||
if left < 0 or not pending: | |||
yield carry[1:] | |||
else: | |||
for k in pending: | |||
yield from generate_combos(pending - {k}, left - costs[carry[-1], k], carry + (k,)) | |||
combos = generate_combos({k for k, v in rates.items() if v}, lim=30) | |||
best = max(combos, key=for_one) | |||
print(best) | |||
print(for_one(best)) | |||
# 2615 |
@@ -0,0 +1,97 @@ | |||
from itertools import cycle | |||
SCHEME = ''' | |||
#### | |||
.#. | |||
### | |||
.#. | |||
..# | |||
..# | |||
### | |||
# | |||
# | |||
# | |||
# | |||
## | |||
## | |||
''' | |||
def generate_blocks(scheme): | |||
blocks = [] | |||
for block in scheme.strip().split('\n\n'): | |||
blocks.append(set()) | |||
lns = block.splitlines() | |||
for y, row in enumerate(lns[::-1]): | |||
for x, v in enumerate(row, 2): | |||
if v == '#': | |||
blocks[-1].add(complex(x, y)) | |||
return blocks | |||
def play_tetris(width, gusts, blocks, lim=None): | |||
top = 0 | |||
solid = {j for j in range(width)} | |||
contribs = [] | |||
block_cycle = cycle(enumerate(blocks)) | |||
gust_cycle = cycle(enumerate(gusts)) | |||
states = {} | |||
gust_idx = None | |||
for block_idx, block in block_cycle: | |||
# position block 4 up from top | |||
block = {(p + 1j * top) + 4j for p in block} | |||
# identify cycle | |||
if lim is None: | |||
state = tuple(complex(x, top - dy) in solid for x in range(7) for dy in range(1)) | |||
key = block_idx, gust_idx, state | |||
if key in states: | |||
cycle_idx = states.get(key) | |||
return contribs[:cycle_idx], contribs[cycle_idx:] | |||
states[key] = len(states) | |||
# play | |||
for gust_idx, gust in gust_cycle: | |||
# move sideways | |||
dx = {'<': -1, '>': 1}[gust] | |||
new = {p + dx for p in block} | |||
if not any(p in solid or p.real in {-1, width} for p in new): | |||
block = new | |||
# move down | |||
dy = -1j | |||
new = {p + dy for p in block} | |||
if any(p in solid for p in new): | |||
solid |= block | |||
break | |||
else: | |||
block = new | |||
contribs.append(int(max(abs(p.imag) for p in solid)) - top) | |||
top = sum(contribs) | |||
if len(contribs) == lim: | |||
return contribs | |||
def main(): | |||
width = 7 | |||
gusts = open(0).read().strip() | |||
blocks = generate_blocks(SCHEME) | |||
contribs = play_tetris(width, gusts, blocks, lim=2022) | |||
print(sum(contribs)) | |||
head, tail = play_tetris(width, gusts, blocks) | |||
N = 1_000_000_000_000 - len(head) | |||
print(sum(head) + sum(tail) * N // len(tail) + sum(tail[:N % len(tail)])) | |||
main() |