aW1wb3J0IHN5cwpzeXMuc2V0cmVjdXJzaW9ubGltaXQoNTAwMCkKCmRlZiBnZXRfdmFyaWFudHMocG9seSk6CiAgICB2YXJpYW50cyA9IFtdCiAgICBjdXJyZW50ID0gbGlzdChwb2x5KQogICAgZm9yIF8gaW4gcmFuZ2UoMik6CiAgICAgICAgZm9yIF8gaW4gcmFuZ2UoNCk6CiAgICAgICAgICAgIHZhcmlhbnRzLmFwcGVuZCh0dXBsZShzb3J0ZWQoY3VycmVudCkpKQogICAgICAgICAgICBjdXJyZW50ID0gWyh5LCAteCkgZm9yIHgsIHkgaW4gY3VycmVudF0KICAgICAgICBjdXJyZW50ID0gWyh4LCAteSkgZm9yIHgsIHkgaW4gY3VycmVudF0KICAgIHVuaXF1ZV92YXJpYW50cyA9IHNldCgpCiAgICBmb3IgdiBpbiB2YXJpYW50czoKICAgICAgICBtaW5feCA9IG1pbihwWzBdIGZvciBwIGluIHYpCiAgICAgICAgbWluX3kgPSBtaW4ocFsxXSBmb3IgcCBpbiB2KQogICAgICAgIG5vcm0gPSB0dXBsZShzb3J0ZWQoKHggLSBtaW5feCwgeSAtIG1pbl95KSBmb3IgeCwgeSBpbiB2KSkKICAgICAgICB1bmlxdWVfdmFyaWFudHMuYWRkKG5vcm0pCiAgICByZXR1cm4gdW5pcXVlX3ZhcmlhbnRzCgpkZWYgY2Fub25pY2FsKHBvbHkpOgogICAgcmV0dXJuIG1pbihnZXRfdmFyaWFudHMocG9seSkpCgptZW1vX2dlbiA9IHt9CmRlZiBnZXRfZnJlZV9wb2x5b21pbm9lcyhuKToKICAgIGlmIG4gaW4gbWVtb19nZW46IHJldHVybiBtZW1vX2dlbltuXQogICAgaWYgbiA9PSAxOgogICAgICAgIHJldHVybiB7KCgwLDApLCl9CiAgICBwcmV2ID0gZ2V0X2ZyZWVfcG9seW9taW5vZXMobi0xKQogICAgcmVzdWx0ID0gc2V0KCkKICAgIGZvciBwIGluIHByZXY6CiAgICAgICAgcF9zZXQgPSBzZXQocCkKICAgICAgICBmb3IgeCwgeSBpbiBwX3NldDoKICAgICAgICAgICAgZm9yIGR4LCBkeSBpbiBbKDAsMSksICgwLC0xKSwgKDEsMCksICgtMSwwKV06CiAgICAgICAgICAgICAgICBueCwgbnkgPSB4K2R4LCB5K2R5CiAgICAgICAgICAgICAgICBpZiAobngsIG55KSBub3QgaW4gcF9zZXQ6CiAgICAgICAgICAgICAgICAgICAgbmV3X3AgPSBwX3NldCB8IHsobngsIG55KX0KICAgICAgICAgICAgICAgICAgICByZXN1bHQuYWRkKGNhbm9uaWNhbChuZXdfcCkpCiAgICBtZW1vX2dlbltuXSA9IHJlc3VsdAogICAgcmV0dXJuIHJlc3VsdAoKZGVmIGhhc19oYW1pbHRvbmlhbihwb2x5KToKICAgIG4gPSBsZW4ocG9seSkKICAgIGFkaiA9IHtwdDogW10gZm9yIHB0IGluIHBvbHl9CiAgICBmb3IgeCwgeSBpbiBwb2x5OgogICAgICAgIGZvciBkeCwgZHkgaW4gWygwLDEpLCAoMCwtMSksICgxLDApLCAoLTEsMCldOgogICAgICAgICAgICBuYiA9ICh4K2R4LCB5K2R5KQogICAgICAgICAgICBpZiBuYiBpbiBwb2x5OgogICAgICAgICAgICAgICAgYWRqWyh4LHkpXS5hcHBlbmQobmIpCiAgICBmb3IgcHQgaW4gcG9seToKICAgICAgICBpZiBub3QgYWRqW3B0XSBhbmQgbiAmZ3Q7IDE6IHJldHVybiBGYWxzZQogICAgZGVnMSA9IFtwdCBmb3IgcHQgaW4gcG9seSBpZiBsZW4oYWRqW3B0XSkgPT0gMV0KICAgIGlmIGxlbihkZWcxKSAmZ3Q7IDI6IHJldHVybiBGYWxzZQogICAgc3RhcnRfbm9kZXMgPSBkZWcxIGlmIGRlZzEgZWxzZSBsaXN0KHBvbHkpCiAgICBwb2x5X2xpc3QgPSBsaXN0KHBvbHkpCiAgICBpZHhfbWFwID0ge3B0OiBpIGZvciBpLCBwdCBpbiBlbnVtZXJhdGUocG9seV9saXN0KX0KICAgIGRlZiBkZnModV9pZHgsIHZpc2l0ZWRfbWFzaywgY291bnQpOgogICAgICAgIGlmIGNvdW50ID09IG46IHJldHVybiBUcnVlCiAgICAgICAgdSA9IHBvbHlfbGlzdFt1X2lkeF0KICAgICAgICBmb3IgdiBpbiBhZGpbdV06CiAgICAgICAgICAgIHZfaWR4ID0gaWR4X21hcFt2XQogICAgICAgICAgICBpZiBub3QgKHZpc2l0ZWRfbWFzayAmYW1wOyAoMSAmbHQ7Jmx0OyB2X2lkeCkpOgogICAgICAgICAgICAgICAgaWYgZGZzKHZfaWR4LCB2aXNpdGVkX21hc2sgfCAoMSAmbHQ7Jmx0OyB2X2lkeCksIGNvdW50ICsgMSk6CiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIFRydWUKICAgICAgICByZXR1cm4gRmFsc2UKICAgIGZvciBzdGFydCBpbiBzdGFydF9ub2RlczoKICAgICAgICBzX2lkeCA9IGlkeF9tYXBbc3RhcnRdCiAgICAgICAgaWYgZGZzKHNfaWR4LCAxICZsdDsmbHQ7IHNfaWR4LCAxKToKICAgICAgICAgICAgcmV0dXJuIFRydWUKICAgIHJldHVybiBGYWxzZQoKcHJpbnQoJnF1b3Q7U1RBUlRfQ0FMQyZxdW90OykKZm9yIG4gaW4gWzIsIDQsIDYsIDgsIDEwXToKICAgIHBvbHlzID0gZ2V0X2ZyZWVfcG9seW9taW5vZXMobikKICAgIGNvdW50ID0gMAogICAgZm9yIHAgaW4gcG9seXM6CiAgICAgICAgaWYgaGFzX2hhbWlsdG9uaWFuKHApOgogICAgICAgICAgICBjb3VudCArPSAxCiAgICBwcmludChmJnF1b3Q7U2l6ZSB7bn06IHtjb3VudH0mcXVvdDspCnByaW50KCZxdW90O0VORF9DQUxDJnF1b3Q7KQ==
import sys
sys.setrecursionlimit(5000)
def get_variants(poly):
variants = []
current = list(poly)
for _ in range(2):
for _ in range(4):
variants.append(tuple(sorted(current)))
current = [(y, -x) for x, y in current]
current = [(x, -y) for x, y in current]
unique_variants = set()
for v in variants:
min_x = min(p[0] for p in v)
min_y = min(p[1] for p in v)
norm = tuple(sorted((x - min_x, y - min_y) for x, y in v))
unique_variants.add(norm)
return unique_variants
def canonical(poly):
return min(get_variants(poly))
memo_gen = {}
def get_free_polyominoes(n):
if n in memo_gen: return memo_gen[n]
if n == 1:
return {((0,0),)}
prev = get_free_polyominoes(n-1)
result = set()
for p in prev:
p_set = set(p)
for x, y in p_set:
for dx, dy in [(0,1), (0,-1), (1,0), (-1,0)]:
nx, ny = x+dx, y+dy
if (nx, ny) not in p_set:
new_p = p_set | {(nx, ny)}
result.add(canonical(new_p))
memo_gen[n] = result
return result
def has_hamiltonian(poly):
n = len(poly)
adj = {pt: [] for pt in poly}
for x, y in poly:
for dx, dy in [(0,1), (0,-1), (1,0), (-1,0)]:
nb = (x+dx, y+dy)
if nb in poly:
adj[(x,y)].append(nb)
for pt in poly:
if not adj[pt] and n > 1: return False
deg1 = [pt for pt in poly if len(adj[pt]) == 1]
if len(deg1) > 2: return False
start_nodes = deg1 if deg1 else list(poly)
poly_list = list(poly)
idx_map = {pt: i for i, pt in enumerate(poly_list)}
def dfs(u_idx, visited_mask, count):
if count == n: return True
u = poly_list[u_idx]
for v in adj[u]:
v_idx = idx_map[v]
if not (visited_mask & (1 << v_idx)):
if dfs(v_idx, visited_mask | (1 << v_idx), count + 1):
return True
return False
for start in start_nodes:
s_idx = idx_map[start]
if dfs(s_idx, 1 << s_idx, 1):
return True
return False
print("START_CALC")
for n in [2, 4, 6, 8, 10]:
polys = get_free_polyominoes(n)
count = 0
for p in polys:
if has_hamiltonian(p):
count += 1
print(f"Size {n}: {count}")
print("END_CALC")