ノノグラムラインのブルートフォース解法


25

バックグラウンド

PiogramまたはGriddlersとも呼ばれるNonogramは、各行の連続した色付きセルの数を使用して、2Dグリッド上の各セルを色付けするか空白のままにするかを決定するパズルです。

以下は、ソリューションを使用したNonogramパズルの例です。

問題は、一部の市販のNonogramゲーム/モバイルアプリには、手で解決できないパズルがある(たとえば、複数のソリューションがある、または深いバックトラッキングが必要)ことです。ただし、それらはプレイヤーにいくつかの命を提供します正解が空白のセルを着色しようとすると1つの命が失われます。だから今、それらの厄介な「パズル」をブルートフォースする時です!

タスクを単純化するために、その手がかりと他の何もない1行だけを想像してください。

3 7 | _ _ _ _ _  _ _ _ _ _  _ _ _ _ _

[3,7]手がかりであり、ラインの長さは、15個の細胞です。複数の可能な解決策があるため、この線を完全に解決する(つまり、すべての色の付いたセルを決定する)ために、いくつかの命を危険にさらす必要があります。

チャレンジ

手がかり(正の整数のリスト)と行の長さのある行が与えられた場合、最適な戦略で行を総当たり攻撃すると仮定して、失う命の最大数を見つけます。

色付きのセルを常に推測することに注意してください。実際のゲームでは、空のセル(正しいか間違っているか)を推測してもあなたの生活には影響しません。そのため、パズルをそのように「解決」することはできません。

また、入力は常に有効な非グラム行を表すと想定できるため、などのことを心配する必要はありません[6], 5

説明

最初にいくつかのより簡単な例を見てみましょう。

[1,2], 5

この行にOは、正確に3つの可能性があります(色付きのセル、.空のセル):

O . O O .
O . . O O
. O . O O

セル0(左からの0から始まるインデックス)を着色しようとすると、次のいずれかが発生します。

  • セルは正しく色付けされています。2つの可能性があり、セルを完全に解決するために、セル2とセル4を選択できます。どちらの場合でも、最悪の場合には1つの命を失います。
  • セルは空であり、命を失います。この場合、このラインに対する独自のソリューションがすでに特定されているため、1人の命が失われました。

したがって、答え[1,2], 5は1です。

[5], 10

バイナリ検索?いや。

最も明白な最初の選択肢は4または5で、空白の場合(1ライフのコストで)1つの可能性が明らかになります。最初に4を選択したとしましょう。セル4が実際に色付けされている場合、ライフが失われるまで(または、セル0が色付けされ、その後、ライフをまったく消費しなくなるまで)3、2、1、0を左に延長します。命が失われるたびに、たとえば次のようなものが見られる場合、解決策を一意に決定できます。

_ _ X O O _ _ _ _ _

答えは次のとおりです。

. . . O O O O O . .

したがって、に対する答え[5], 10も1です。

[3,7], 15

セル11から開始します。空の場合、次のソリューションがすぐに表示されます。

O O O . O O O O O O O X . . .

次に、12を試します。空の場合、2つの可能性が得られますが、1つの余命を犠牲にして解決できます。

O O O . . O O O O O O O X . .
. O O O . O O O O O O O X . .

次に2を試してください。空の場合は、[1,2], 5例と同様に解決できる3つの可能性につながります。

. . X O O O . O O O O O O O .
. . X O O O . . O O O O O O O
. . X . O O O . O O O O O O O

この方法でリスクを最小限に抑え続けると、最大であらゆるソリューションに到達できます。2人の命が費やされた。

テストケース

[1,2] 5 => 1
[2] 5 => 2
[1] 5 => 4
[] 5 => 0
[5] 10 => 1
[2,1,5] 10 => 0
[2,4] 10 => 2
[6] 15 => 2
[5] 15 => 2
[4] 15 => 3
[3,7] 15 => 2
[3,4] 15 => 3
[2,2,4] 15 => 4
[1,1,1,1,1,1,1] 15 => 2

[2,1,1,3,1] 15 => 3
[1,1,1,2,1] 15 => 5

最後の2つの場合、最適な戦略は最小の空白を通過するのではなく、左から右(または右から左)に移動するだけです。指摘してくれた@crashozに感謝します。

ルール

標準の規則が適用されます。バイト単位の最短の有効な送信が優先されます。

バウンティ

誰かが多項式時間アルゴリズム(正確性の証明付き)を思いついたら、そのような解決策に対して+100バウンティを授与します。


目的の出力は[6], 5何ですか?
リーキー修道女

推測するとき、セルが黒であると推測する必要がありますか、それとも黒または白を推測できますか?
feersum

@LeakyNun無効な行です。入力は常に有効な非グラム行であると想定できます。
バブラー

@feersum常に色付きのセルを推測します。実際のゲームでは、空のセル(正しいか間違っているか)を推測してもあなたの生活には影響しないため、フィードバックを受け取ることはできません。
バブラー

素晴らしい挑戦
エンリコボルバ

回答:


19

ルビー、85バイト

f=->l,n,s=n-l.sum-l.size+1{*a,b=l;b&&s>0?(a[0]?1+f[a,n-b-2,s-1]:(n.to_f/b).ceil-1):0}

オンラインでお試しください!

説明

l=[l1,l2,...,lx]xn

lx
nlx
nlx1+f(l,nlx)
1+f(l~,nlx2)l~l

f(l,n)={0,if 1xli+x1nnlx1if x=11+max{f(l,nlx)f(l~,nlx2),otherwise

_未知の例X、既知の空間、O既知の色の細胞、そしてL命が失われた例

[2,2,4] 15                  _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
(1) -> [2,2,4] 11           _ _ _ _ _ _ _ _ _ _ _ L X X X
    (1) -> [2,2,4] 7        _ _ _ _ _ _ _ L X X X L X X X
        0                   X X X L X X X L X X X L X X X
    (2) -> [2,2] 5          _ _ _ _ _ X O O O O L L X X X
        0                   O O X O O X O O O O L L X X X 
(2) -> [2,2] 9              _ _ _ _ _ _ _ _ _ X O O O O L
    (1) -> [2,2] 7          _ _ _ _ _ _ _ L X X O O O O L
        (1) -> [2,2] 5      _ _ _ _ _ L X L X X O O O O L
            0               O O X O O L X L X X O O O O L
        (2) -> [2] 3        _ _ _ X O O L L X X O O O O L
            1               O O L X O O L L X X O O O O L               
    (2) -> [2] 5            _ _ _ _ _ X O O L X O O O O L
        2                   O O L L X X O O L X O O O O L

失われた命の数を得るために、バイナリツリーを作成します。ツリーの最大の高さを数えるだけです。ただし、すべてのブランチを評価するため、これによりになります。もっとうまくやれる。O(2n)

各ステップで適切なブランチを「選択」するのに役立つコスト関数を定義しましょう。h

h(l,n)=n1xlix+1

hは、すべての手がかりを間に1つのスペースを入れて詰め込んだ場合に、余分なスペースの数をカウントします。したがって、それは本質的に、インスタンスを解決するために必要な生命の量の指標であり、それが高いほど、より多くの生命が必要です。アイデアは、再帰インジケーターにこのインジケーターを適用することです。

定義により、 h

h(l,nlx)=nlx1xlix+1=(n1xlix+1)lx=h(l,n)lx

そして、

h(l~,nlx2)=nlx21x1li(x1)+1=(n1xlix+1)1=h(l,n)1

次に、

h(l,n)={0,if 1xli+x1nnlx,if x=1max{h(l,nlx)+lxh(l~,nlx2)+1,otherwise

最悪の場合を取得するために各ステップでを最大化し たいので、繰り返しの2つの式の違いを確認しましょうh

[h(l,nlx)+lx][h(l~,nlx2)+1]=nlxn1xlix+1+lx[nlx21x1li(x1)+1+1]=2

[h(l,nlx)+lx][h(l~,nlx2)+1]=2[h(l,nlx)+lx][h(l~,nlx2)+1]<0[h(l,nlx)+lx]<[h(l~,nlx2)+1]
したがって、2番目の式は常に最大であり、最初の式を削除できます

h(l,n)={0,if 1xli+x1nnlx,if x=1h(l~,nlx2)+1otherwise

最後に、この再帰的な定義は、関数オプション(2)が常に最悪のケースであることを示しています可能性の最大数を与える、つまり最大化する)hfh

f(l,n)={0,if 1xli+x1nnlx1if x=11+f(l~,nlx2),otherwise

すべてのステップでを少なくとも3ずつ減らし、再帰呼び出しが1つあります。複雑さはOn nO(n)


2
信じられないほどの最初の投稿、PPCGへようこそ!
コール

1
@cole彼らの最初の投稿ではありませんが、確かに素晴らしいです!非常に巧妙なアプローチ+1
Mr Xcoder

1
お見事。それまで深刻な論理的欠陥を見つけられなかった場合、2日後に賞金を授与します。
バブラー

2

Python、303 289バイト

久しぶりの最初のゴルフなので、余分な脂肪がたくさんあるかもしれません。(14バイトに値するJo Kingに感謝します。)

関数fは、可能なすべての配置を生成します(ただし、常に最初の文字として空白を使用しますが、呼び出す前に長さを1インクリメントする限りは問題ありません)。関数gは、空白と再帰の数が最も少ない位置を選択します。関数hはそれらをまとめます。

f=lambda l,n:["."*i+"X"*l[0]+c for i in range(1,n-l[0]+1)for c in f(l[1:],n-i-l[0])]if l else["."*n]
def g(q,n):O,X=min([[[p[:i]+p[i+1:]for p in q if p[i]==u]for u in".X"]for i in range(n)],key=lambda x:len(x[0]));return(len(q)>1)*1and max(1+g(O,n-1),g(X,n-1))
h=lambda l,n:g(f(l,n+1),n+1)

例はすべて正常に実行されます。

>>> h([3,7],15)
2
>>> h([3,4],15)
3
>>> h([1,1,1,2,1],15)
6


1
あなたは返すために許可されているFalseため0?その場合は、変更することができます(len(q)>1)*1andlen(q)>1and。あなたが返すことが許されていない場合Falseのために0、その後にそれを行う、が、変更g(f(l,n+1),n+1)する1*g(f(l,n+1),n+1)と、それは1バイト節約まだ意志
ザカリー

1
さらに良い:の場合、False許可されていません0に変更g(f(l,n+1),n+1)する代わりに1*g(f(l,n+1),n+1)、それを変更する+g(f(l,n+1),n+1)
ザカリー

2
また、h=バイト数をカウントする必要はありません
ザカリー

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.