Project Euler 161

プロジェクトオイラー
http://projecteuler.net/index.php

Q161.
2種類のトリオミノを9×12の格子に敷き詰める方法は何通りあるか。

最初、深さ優先探索をしたら時間的に話にならなかった。
すぐに、状態とそれが何通りあるかを記憶していけばそれなりにいけるのではないかと思ったが、なんとなくめんどくさくて放っておいた。少し精神的に余裕が出来たのでやってみたら、簡単にできた。
敷き詰めた状態は108ビットあれば表せるので、最初は4つの整数の配列を使っていたが、多倍長整数にしたら、少し速くなった。このほうがコードはすっきり。それから、トリオミノは上から順に敷き詰めていったが、全体が縦長の長方形と考えるほうがかなり速くなる。



triomino = (
(0, 1, 4), (0, 1, 5), (0, 4, 5), (0, 3, 4), (0, 4, 8), (0, 1, 2)
)

domain = (
( 0, 1, 1), (0, 1, 1), (0, 1, 1),
(-1, 0, 1), (0, 0, 2), (0, 2, 0)
)

class Grid:
def __init__(self, w, h, s = 0):
self.w = w
self.h = h
self.a = s
self.t = [ ]
self.num_solution = 0

def is_fit(self, k, pos):
if not self.is_inside(k, pos): # O.B
return False

for e in triomino[k]:
if not self.is_empty(self.offset_pos(pos, e)):
return False
return True

def is_inside(self, k, pos):
if domain[k][0] + pos % self.w < 0: # left
return False
if domain[k][1] + pos % self.w >= self.w: # right
return False
if domain[k][2] + pos / self.w >= self.h: # bottom
return False
return True

def offset_pos(self, pos, e):
if e == 3:
pos += self.w - 1
else:
pos += (e & 3) + self.w * (e >> 2)
return pos

def push(self, k, pos):
for e in triomino[k]:
self.add_square(self.offset_pos(pos, e))
self.t.append( (k, pos))

def pop(self):
k, pos = self.t.pop()
for e in triomino[k]:
self.remove_square(self.offset_pos(pos, e))

def get_pos(self):
i = 0
while self.a & (1 << i):
i += 1
return i

def is_empty(self, p):
return (self.a & (1 << p)) == 0

def add_square(self, p):
self.a |= 1 << p

def remove_square(self, p):
self.a ^= 1 << p

w = 9
h = 12
N = w * h / 3
g = Grid(w, h)
stats = { 0:1 }
for i in range(w * h / 3):
stats_next = { }
for s, n in stats.iteritems():
g1 = Grid(w, h, s)
pos = g1.get_pos()
for k in range(6):
if g1.is_fit(k, pos):
g1.push(k, pos)
if g1.a in stats_next:
stats_next[g1.a] += n
else:
stats_next[g1.a] = n
g1.pop()
stats = stats_next

print sum(stats.values())