0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

パズルAdvent Calendar 2017

Day 24

組合せ最適化でへやわけを解く

Last updated at Posted at 2017-12-23

Advent Calendar 23日目の記事 組合せ最適化で波及効果を解く
Advent Calendar 25日目の記事 組合せ最適化でオーノーを解く

これなに

へやわけを、Pythonで組合せ最適化モデルを作って解きます。
解く楽しみは、モデル化を工夫することになります。

自分でも試してみたい人は、下記を参考にしてください。

問題

  • 盤面のいくつかのマスを黒くぬります
  • 太線で区切られた四角(部屋)に入っている数字は、その部屋に入る黒マスの数を表します
  • 数字の入っていない部屋は、いくつ黒マスが入るか不明です
  • 白マスを、タテまたはヨコにまっすぐに3つ以上の部屋にわたって続けさせてはいけません
  • 黒マスをタテヨコに連続させたり、黒マスで盤面を分断したりしてはいけません

入力パラメータ

dataにヒントが入っているとします。

python
import numpy as np
from collections import defaultdict
from itertools import chain, groupby, product
from pulp import LpProblem, lpSum, value
from ortoolpy import addbinvars, unionfind
data = np.array([list(s) for s in """\
AABCCCDDDD
AABCCCEEEF
GGGGHHIIIF
GGGGHHIIIF
JKLLHHIIIF
JKLLMMMNNN
JOLLMMMNNN
PQQRRRSSTT
PQQUUUSSVV
PQQUUUSSVV""".splitlines()])
nums = {'A':2, 'I':5, 'K':0, 'L':1, 'N':2, 'O':1, 'P':2, 'U':3}
nw, nh = len(data[0]), len(data)

Pythonで解く

数理モデルを作成し、解きましょう。

変数

  • v:0:white, 1:black (1)

制約

  • 3つの部屋で白をまっすぐ連続してはいけません (2)
  • 数字は部屋内の黒の数となること (3)
  • 黒は連続しないこと (4)
  • 黒で分断しないこと (5)
python
m = LpProblem()
v = np.array(addbinvars(nh, nw)) # 0:white, 1:black (1)
for d, x in chain(zip(data,v), zip(data.T,v.T)):
    b = np.arange(len(d)-1)[d[1:] != d[:-1]]
    for i, j in zip(b, b[1:]+2):
        m += lpSum(x[i:j]) >= 1 # (2)
for k, d in groupby(sorted(zip(data.flat, v.flat)), lambda x:x[0]):
    if k in nums:
        m += lpSum(c[1] for c in d) == nums[k] # (3)
for e in chain((v[1:,:] + v[:-1,:]).flat, (v[:,1:] + v[:,:-1]).flat):
    m += e <= 1 # (4)
def dirs(i, j):
    return [(i+y-x)*nw + j+y+x-1 for y in range(2) for x in range(2)
        if 0 <= i+y-x < nh and 0 <= j+y+x-1 < nw]
while True:
    m.solve()
    r = np.vectorize(value)(v).astype(int)
    u = unionfind(nh * nw)
    if unionfind.isconnected(1-r, u):
        break
    dc = defaultdict(list)
    for i, j in product(range(nh), range(nw)):
        if r[i,j]:
            for l in set(u.find(k) for k in dirs(j, i)):
                dc[l].append(v[i][j])
    for s in dc.values():
        m += lpSum(s) <= len(s) - 1 # (5)

結果の表示

python
data[r==1] = '#'
print('\n'.join(' '.join(i) for i in data))
>>>
# A B # C C D # D #
A # B C C # E E E F
G G # G H H # I # F
G G G # H H I # I F
# K L L # H # I # F
J K # L M # M N N N
J # L L # M M # N #
# Q Q R R R # S T T
P Q Q # U # S S # V
# Q Q U # U S # V V

解けていることが確認できます。

以上

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?