爆弾投下アルゴリズム


212

n x m非負の整数で構成される行列があります。例えば:

2 3 4 7 1
1 5 2 6 2
4 3 4 2 1
2 1 2 4 1
3 1 3 4 1
2 1 4 3 2
6 9 1 6 4

「爆弾の投下」は、ターゲットセルの数とその隣接セルの8つすべての数を1つ減らし、最低ゼロにします。

x x x 
x X x
x x x

すべてのセルをゼロに減らすために必要な爆弾の最小数を決定するアルゴリズムは何ですか?

Bオプション(注意深い読者ではないため)

実際、問題の最初のバージョンは私が答えを求めているものではありません。タスク全体を注意深く読みませんでした。追加の制約があります。

単純な問題についてはどうでしょうか。行のシーケンスが増加していない必要がある場合:

8 7 6 6 5 可能な入力シーケンスです

7 8 5 5 2 7-> 8が順に成長するため、不可能です。

多分「より簡単な」ケースの答えを見つけることは、より難しいケースの解決策を見つけるのに役立つでしょう。

PS:いくつかの同じ状況で、上の線をクリアするために最小限の爆弾が必要な場合、列の「左側」にある爆弾のほとんどを使用する爆弾を選択すると思います。それでも正しいかもしれない証拠はありますか?


4
例2のように一部のフィールドをスキップできることに気づきました。グローバルに機能させる方法を見つける(正しい方法の場合)。2つをクリアするには、2つの爆弾を隣人のいずれかに落とし、5つに他のセットの損傷を含めます。しかし、それを書き換えるとき(減少した後)には、2つの選択肢があります(1組の損傷はありません)。
abc

23
ひょっとするとこれはNP難しいですか?これは最大カバレッジ問題のバリアントのようです。
Mysticial 2013年

14
+1考えて面白いものを与えてくれた
Nick Mitchinson 2013年

3
@Kostek、大きな問題!リンクを投稿してください。
パニック大佐2013年

5
おそらく明確にする必要があります。質問は次のとおりですwhat's the minimum amount of bombs required to clean the board?。これは、実際の爆撃パターンを見つけるために必ずしも必要ではないが、爆弾の最小数だけを意味するのですか?
Lie Ryan

回答:


38

これを単純な副問題に減らす方法があります。

説明、アルゴリズム、およびアルゴリズムが最適なソリューションを提供する理由には2つの部分があります。1つ目は2つ目がなければ意味がありません。その理由から始めます。

長方形を爆撃することを考えた場合(大きな長方形を想定-エッジケースはまだない)、周囲の正方形の中空の長方形を0に減らす唯一の方法は、周囲を爆撃するか、または中空の長方形を爆撃することです。境界のすぐ内側の正方形。境界レイヤーを1と呼び、その内側の長方形をレイヤー2と呼びます。

重要な洞察は、レイヤー1を爆撃するポイントがないということです。そうすることで得られる「爆風半径」は常にレイヤー2の別の正方形の爆風半径内に含まれているためです。これを簡単に理解できるはずです。

したがって、問題を減らして、周囲を爆撃する最適な方法を見つけ、すべての正方形が0になるまでそれを繰り返すことができます。

しかし、もちろん、それが最適ではない方法で周囲を爆撃することが可能である場合、常に最適な解決策を見つけるわけではありませんが、X個の追加の爆弾を使用することで、> X個の爆弾によって内層を減らす問題がより簡単になります。したがって、許可レイヤーを1と呼び、レイヤー2のどこかに(レイヤー1のすぐ内側に)追加のX爆弾を配置した場合、後でレイヤー2を爆撃する労力をXより多く削減できますか?言い換えれば、私たちは、外周を減らすことに貪欲になることができることを証明しなければなりません。

しかし、私たちは貪欲になることができることを知っています。レイヤー2の爆弾は、レイヤー3に戦略的に配置された爆弾よりも効率的にレイヤー2を0に減らすことができないためです。また、以前と同じ理由で、すべての正方形に影響を与える爆弾が常にレイヤー3に配置できます。爆弾がレイヤー2に置くことができるレイヤー2です。だから、貪欲になることは私たちに決して害を及ぼすことはありません(この欲望の意味で)。

したがって、次の内側のレイヤーを爆撃して許可者を0に減らすための最適な方法を見つけるだけです。

内側のレイヤーのコーナーのみが到達できるため、最初にコーナーを0に爆撃することによって傷つくことはありません。したがって、実際に選択の余地はありません(そして、コーナーに到達できる周囲の爆弾には爆風半径が含まれています。内層のコーナーからの爆風半径)。

これが完了すると、0コーナーに隣接する外周の正方形に到達するには、内層から2つの正方形だけが到達します。

0       A       B

C       X       Y

D       Z

この時点で、爆弾は隣接する3つの正方形を減らすため、境界線は事実上1次元の閉じたループになります。角の近くの奇妙さを除いて-XはA、B、C、およびDを「ヒット」できます。

これで爆風半径のトリックを使用できなくなりました-奇妙なコーナーを除いて各正方形の状況は対称的であり、爆風半径がなくても別のサブセットです。これが閉ループの代わりに(パニック大佐が論じるように)ラインであった場合、解決策は簡単です。爆破半径はスーパーセットであるため、端点は0に減らす必要があり、端点に隣接する点を爆撃することに害を及ぼすことはありません。エンドポイントを0にしても、新しいエンドポイントがあるので、繰り返します(ラインがすべて0になるまで)。

したがって、レイヤー内の1つの正方形を最適に0に減らすことができる場合は、アルゴリズムがあります(ループをカットし、端点を持つ直線があるため)。最小値の正方形に隣接する爆撃(2つのオプションを与える)は、その最小値の2正方形内の最大値が可能な限り最小になるようにします(これを管理するために爆撃を分割する必要がある場合があります)が最適ですが、 (まだ?)証拠はありません。


+1-私は似たようなものを書くつもりでした。あなたはそれを持っていると思います!
Rex Kerr

5
@ビーカー、問題を注意深く読んでください。正方形を爆撃すると、隣接する8つすべてが減少するため、彼の仮定は実際に正しいです。
darksky 2013年

20
But, we do know we can be greedy...-私はこれを購入しません。1 1 2 1 1 2境界を考慮してください。爆弾の最小数は4ですが、3つの異なる解決策あります。 各ソリューションは、次のレイヤーに異なる影響を与えます。境界に複数の最小限のソリューションがある限り、内層を考慮せずに境界を完全に分離することはできません。この問題はバックトラックなしでは解決できないと思います。
user1354557 2013年

4
私はこの解決策について考えていましたが、それはそれほど単純に見えません。爆弾をレイヤー2にドロップしてレイヤー1をクリーニングできることは事実ですが、複数のソリューションがある場合、それらは上位レイヤーのソリューションに影響します。
Luka Rahne 2013年

12
@psr:これは機能しません。外層に最適な爆撃方法は、全体的に最適ではない可能性があります。例:0011100 0100010 0000000 0000000 1110111。最初のレイヤーを爆撃する最適な方法は、2列目の真ん中に爆撃し、合計3つの爆弾を使って外側のレイヤーを殺すことです。ただし、次の層を処理するには2つの爆弾が必要です。Optimalは合計4つの爆弾しか必要としません。最初の2つの列に2つ、最後の列に2つです。
nneonneo

26

Pólya氏は、「問題を解決できない場合は、簡単に解決できる問題があります。それを見つけてください」と言います。

明らかに単純な問題は1次元の問題です(グリッドが単一行の場合)。最も単純なアルゴリズムから始めましょう-最大のターゲットを貪欲に爆撃します。これはいつ失敗するのですか?

与えられた1 1 1場合、貪欲なアルゴリズムは、最初に爆撃するセルに無関心です。もちろん、中央のセルのほうが優れています。3つのセルすべてを一度にゼロにします。これは、新しいアルゴリズムA「残りの合計を最小化する爆弾」を示唆しています。このアルゴリズムはいつ失敗しますか?

が与えられた場合1 1 2 1 1、アルゴリズムAは2番目、3番目、または4番目のセルの爆撃に無関心です。ただし、2番目のセルを爆撃すること0 0 1 1 1は、3番目のセルを爆撃することよりも優れてい1 0 1 0 1ます。それを修正するには?3番目のセルの爆撃の問題は、左に作業し、右に作業することになるため、別々に行う必要があります。

「残りの合計を最小化するために爆撃しますが、(爆撃した場所の)左側の最小値と右側の最小値を最大化します。」このアルゴリズムをBと呼びます。このアルゴリズムはいつ失敗しますか?


編集:コメントを読んだ後、はるかに興味深い問題は、一次元の問題が変更されて端が結合することになることに同意します。その上でどんな進展も見てみたいです。


40
なぜこの回答が多くの賛成票を獲得しているのかはわかりません。1Dケースはほとんど取るに足らないものであり、常に最初のポジティブエレメントの右側にあるエレメントを爆撃するだけです。これは、左側に0のみを含む要素を爆撃する最適な方法が常に1つあるため機能します。これを2Dに拡張して、コーナーの四角形を最適に削除できますが、それを超えて拡張する明確な方法がわかりません...?
BlueRaja-Danny Pflughoeft 2013年

3
@BlueRaja、私は他の回答で議論された貪欲なアプローチが不十分であることを明確に示したので賛成しました(少なくとも、追加の基準で補足する必要がありました)。目標のいくつかの選択は、それらが合計数の同等の減少をもたらすとしても、物事を他のものより広げることになるかもしれません。これは、2Dの問題に対する有益な洞察だと思います。
Tim Goodman

3
そして一般的に、「2Dケースに行き詰まっている場合は、最初に1Dケースを試す」が良いアドバイスです。
Tim Goodman

21
@Tim:「最初に1Dケースを試してみるのが良いアドバイスです」はい、そうです。しかし、それは答えではありません...
BlueRaja-Danny Pflughoeft 2013年

3
1Dのケースは、より高い次元に容易に拡張できない単純なソリューションを持っているので、ここでは少し誤解を招くかもしれませんが、あなたは良い点を持っていると思います。周期的な境界条件を伴う1Dのケース(ラップアラウンドのケース)の方が良いと思います。
Tim Goodman

12

私は時間がないので部分的な解決策だけに立ち止まる必要がありましたが、うまくいけば、この部分的な解決策でさえ、この問題を解決するための1つの潜在的なアプローチに関する洞察が得られるでしょう。

難しい問題に直面したとき、私は問題空間についての直感を開発するために、より単純な問題を思いつくのが好きです。ここで、私が取った最初のステップは、この2次元問題を1次元問題に減らすことでした。次の行を考えます。

0 4 2 1 3 0 1

どういうわけかまたは別の、あなたがでたり周りを爆撃する必要があります知って4爆撃してもメリットがない、低い数字があるスポットの左ので0にそれを得るために4回のスポット04爆撃の上には2。実際に、私は爆撃と信じています(しかし、厳格な証拠が不足している)2まで4スポットが0にダウンすると、その取得するために、他の戦略として、少なくとも良いようです40つまでは戦略に左から右へラインを下に進むことができますこのような:

index = 1
while index < line_length
  while number_at_index(index - 1) > 0
    bomb(index)
  end
  index++
end
# take care of the end of the line
while number_at_index(index - 1) > 0
  bomb(index - 1)
end

爆撃命令の例:

0 4[2]1 3 0 1
0 3[1]0 3 0 1
0 2[0]0 3 0 1
0 1[0]0 3 0 1
0 0 0 0 3[0]1
0 0 0 0 2[0]0
0 0 0 0 1[0]0
0 0 0 0 0 0 0

4[2]1 3 2 1 5
3[1]0 3 2 1 5
2[0]0 3 2 1 5
1[0]0 3 2 1 5
0 0 0 3[2]1 5
0 0 0 2[1]0 5
0 0 0 1[0]0 5
0 0 0 0 0 0[5]
0 0 0 0 0 0[4]
0 0 0 0 0 0[3]
0 0 0 0 0 0[2]
0 0 0 0 0 0[1]
0 0 0 0 0 0 0

何らかの方法で下げる必要がある数値から始めるという考えは、少なくとも他のすべてのソリューション同じくらい優れている主張するソリューションを見つけることが突然可能になるため、魅力的なものです。

複雑さの次のステップは、少なくともこのように優れた検索が依然として実行可能である場合、ボードの端にあります。外縁部を爆撃することによる厳密な利益は決してないことは私には明らかです。スポットを1つ爆撃し、他の3つのスペースを無料で獲得した方がよいでしょう。これを考えると、エッジの内側のリングを爆撃することは、少なくともエッジを爆撃することと同じくらい良いと言えます。さらに、これを、エッジの内側の正しいものを爆撃することが実際にエッジスペースを0に下げる唯一の方法であるという直感と組み合わせることができます。少なくとも他の戦略と同じくらい優れています)。

コーナーピースに関する観察を踏まえると、スターティングボードからすべてのコーナーにゼロがあるボードに移動するための最適な戦略を確実に知っていると言えます。これはそのようなボードの例です(上の2つの線形ボードから数字を借りました)。一部のスペースに異なるラベルを付けました。その理由を説明します。

0 4 2 1 3 0 1 0
4 x x x x x x 4
2 y y y y y y 2
1 y y y y y y 1
3 y y y y y y 3
2 y y y y y y 2
1 y y y y y y 1
5 y y y y y y 5
0 4 2 1 3 0 1 0

一番上の行は、前に見た線形の例に非常によく似ています。上の行をすべて0にする最適な方法は、2番目の行(x行)を爆撃することであるという以前の観察を思い出してください。行のいずれかを爆撃することによって一番上の行をクリアする方法はなく、y行の対応するスペースを爆撃するよりも一番上の行を爆撃することの追加の利点はありませんx

我々は可能性があり(上の対応するスペース爆撃上から直線的な戦略を適用するx自分自身について、行)だけ一番上の行と他には何もして。それはこのようなものになるでしょう:

0 4 2 1 3 0 1 0
4 x[x]x x x x 4
2 y y y y y y 2
1 y y y y y y 1
3 y y y y y y 3
2 y y y y y y 2
1 y y y y y y 1
5 y y y y y y 5
0 4 2 1 3 0 1 0

0 3 1 0 3 0 1 0
4 x[x]x x x x 4
2 y y y y y y 2
1 y y y y y y 1
3 y y y y y y 3
2 y y y y y y 2
1 y y y y y y 1
5 y y y y y y 5
0 4 2 1 3 0 1 0

0 2 0 0 3 0 1 0
4 x[x]x x x x 4
2 y y y y y y 2
1 y y y y y y 1
3 y y y y y y 3
2 y y y y y y 2
1 y y y y y y 1
5 y y y y y y 5
0 4 2 1 3 0 1 0

0 1 0 0 3 0 1 0
4 x[x]x x x x 4
2 y y y y y y 2
1 y y y y y y 1
3 y y y y y y 3
2 y y y y y y 2
1 y y y y y y 1
5 y y y y y y 5
0 4 2 1 3 0 1 0

0 0 0 0 3 0 1 0
4 x x x x x x 4
2 y y y y y y 2
1 y y y y y y 1
3 y y y y y y 3
2 y y y y y y 2
1 y y y y y y 1
5 y y y y y y 5
0 4 2 1 3 0 1 0

このアプローチの欠陥は、最後の2つの爆撃で非常に明白になります。これは、削減だけ爆弾サイトことを考えれば、明らかである42行目の最初の列の数字は最初のものであるxy。最後の2回の爆撃は、最初の爆撃のみを爆撃するよりも明らかに劣っていxます。現在の戦略が最適ではないことを示したので、戦略の変更が明らかに必要です。

この時点で、私は複雑さを減らし、1つのコーナーのみに焦点を当てることができます。これを考えてみましょう:

0 4 2 1
4 x y a
2 z . .
1 b . .

とのスペースを取得する唯一の方法は明らかである4ゼロにまでは、いくつかの組み合わせを爆撃しているxyz。私の心の中でいくつかの曲芸で、私はかなり確か最適解を爆撃することですよxその後、3回とa、その後b。さて、それが私がその解決策にたどり着いた方法を理解することの問題であり、それがこの局所的な問題を解決するために使用できる直観を明らかにするかどうかです。爆弾yzスペースがないことに気づきました。それらのスペースを爆撃することが理にかなっているコーナーを見つけようとすると、次のようなコーナーが生成されます。

0 4 2 5 0
4 x y a .
2 z . . .
5 b . . .
0 . . . .

この場合、最適な解決策はy5回とz5回爆撃することです。さらに一歩進めましょう。

0 4 2 5 6 0 0
4 x y a . . .
2 z . . . . .
5 b . . . . .
6 . . . . . .
0 . . . . . .
0 . . . . . .

ここでは、爆弾ab6回、次にx4回爆破するのが最適なソリューションであると同様に直感的に感じます。

今では、それらの直感を私たちが構築できる原則に変える方法のゲームになります。

うまくいきますように!


10

更新された質問の場合、単純な貪欲アルゴリズムが最適な結果を与えます。

A [0,0]爆弾をセルA [1,1]にドロップし、次にA [1,0]爆弾をセルA [2,1]にドロップし、このプロセスを下向きに続けます。左下隅をきれいにするには、max(A [N-1,0]、A [N-2,0]、A [N-3,0])爆弾をセルA [N-2,1]にドロップします。これにより、最初の3列が完全にクリーンアップされます。

同じ方法で、列3、4、5、次に列6、7、8などをクリーンアップします。

残念ながら、これは元の問題の解決策を見つけるのに役立ちません。


「より大きな」問題(「nonicreasing」制約なし)は、NP困難であることが証明される場合があります。これが証明のスケッチです。

3次までの平面グラフがあるとします。このグラフの最小頂点カバーを見つけてみましょう。ウィキペディアの記事によると、この問題は3次までの平面グラフではNP困難です。平面3SATの硬度-3SATからの削減。これらの証明は両方とも、教授による「Algorithmic Lower Bounds」の最近の講義で提示されています。エリック・デメイン(講義7および9)。

元のグラフ(図の左側のグラフ)の一部のエッジを分割し、それぞれに偶数の追加ノードがある場合、結果のグラフ(図の右側のグラフ)は、元の頂点のまったく同じ最小頂点カバーを持つ必要があります。このような変換により、グラフの頂点をグリッド上の任意の位置に揃えることができます。

ここに画像の説明を入力してください

グラフの頂点を偶数の行と列にのみ配置する場合(1つの頂点に2つのエッジが鋭角を形成しないように)、エッジがある場所に「1」を挿入し、他のグリッド位置に「0」を挿入します。元の問題の任意のソリューションを使用して、最小の頂点カバーを見つけることができます。


左側のグラフはどこから来たのですか?申し訳ありません、あなたの説明はよくわかりません!
ryyst 2013年

1
@ryyst:左からのグラフは、平面グラフの単なる例です。これは、最大4次の平面グラフをグリッド配置のグラフに変換してからn * m行列に変換する方法を示すために使用されます。この行列に適用される「爆弾投下」アルゴリズムは、この変換されたグラフ、つまりその「左」グラフの頂点カバー問題を解決します。
Evgeny Kluev 2013年

ああ、私は今それを手に入れました、そしてあなたの変革は正しいと思います。ありがとう!
ryyst 2013年

@EvgenyKluev、私はあなたが頂点カバーが「4次までの平面グラフ」に対して依然としてNP困難であることを証明する必要があると思う。
Shahbaz 2013年

@Shahbaz:この証明は長すぎると思います。そこで、証明へのリンクを追加しました。
Evgeny Kluev 2014年

9

この問題は整数計画問題として表すことができます。(これはこの問題に取り組むための可能な解決策の1つにすぎません)

ポイントがあります:

a b c d
e f g h
i j k l
m n o p

たとえば、点fが成り立つ16の方程式を書くことができます

f <= ai + bi + ci + ei + fi + gi + ii + ji + ki   

すべてのインデックスと整数解の合計に対して最小化されました。

ソリューションはもちろんこのインデックスの合計です。

これは、すべてのxiを境界0に設定することでさらに簡略化できるため、この例では4 + 1の方程式になります。

問題は、そのような問題を解決するための簡単なアルゴリズムがないことです。私はこれについての専門家ではありませんが、線形計画法はNP困難であるため、この問題を解決することはできません。


8
NPのすべての問題は整数プログラミング問題として定式化できるため、問題がNP-Completeであることがわかっていない限り、これはあまり役に立ちません
BlueRaja-Danny Pflughoeft

1
同意する。また、ソリューションが何かを知るために実行する必要がある正確な動きを知る必要もありません。
Luka Rahne 2013年

1
あなたが0に境界を設定すると、不等式の数はまだ16です
darksky

9

これは部分的な答えです。爆弾の可能性のある下限と上限を見つけようとしています。

3x3以下のボードでは、ソリューションは常に最大の番号のセルです。

4x4より大きいボードでは、最初の明らかな下限はコーナーの合計です。

*2* 3  7 *1*
 1  5  6  2
 2  1  3  2
*6* 9  6 *4*

ただし、爆弾を配置した場合、爆弾が2 + 1 + 6 + 4 = 13未満のこの4x4ボードをクリアすることはできません。

他の回答では、コーナーを排除するために2番目のコーナーに爆弾を配置することは、コーナー自体に爆弾を配置することよりも悪いことは決してないので、ボードを考えると、

*2* 3  4  7 *1*
 1  5  2  6  2
 4  3  4  2  1
 2  1  2  4  1
 3  1  3  4  1
 2  1  4  3  2
*6* 9  1  6 *4*

コーナーの2番目に爆弾を配置して新しいボードを作ることで、コーナーをゼロにすることができます。

 0  1  1  6  0
 0  3  0  5  1
 2  1  1  1  0
 2  1  2  4  1
 0  0  0  0  0
 0  0  0  0  0
 0  3  0  2  0

ここまでは順調ですね。コーナーをクリアするには13の爆弾が必要です。

次に、下にマークされている6、4、3、および2の番号を確認します。

 0  1  1 *6* 0
 0  3  0  5  1
 2  1  1  1  0
*2* 1  2 *4* 1
 0  0  0  0  0
 0  0  0  0  0
 0 *3* 0  2  0

2つを爆撃する方法はありません単一の爆弾を使用してこれらのセルのため、最小爆弾は6 + 4 + 3 + 2増加しているため、コーナーをクリアするために使用した爆弾の数に追加すると、最小値が得られますこのマップに必要な爆弾の数は28爆弾になりました。爆弾数が28未満のこのマップをクリアすることは不可能です。これは、このマップの下限です。

貪欲アルゴリズムを使用して、上限を設定できます。他の回答は、貪欲なアルゴリズムが28の爆弾を使用するソリューションを生成することを示しています。爆弾が28個未満の最適なソリューションはないことが以前に証明されているので、28個の爆弾は確かに最適なソリューションです。

貪欲で、上で述べた最小の境界を見つける方法が収束しない場合、すべての組み合わせのチェックに戻る必要があると思います。

下限を見つけるアルゴリズムは次のとおりです。

  1. 最も大きい番号の要素を選択し、Pという名前を付けます。
  2. Pから2ステップ離れたすべてのセルとP自体をピック不可としてマークします。
  3. minimumsリストにPを追加します。
  4. すべてのセルが選択不可能になるまで、手順1を繰り返します。
  5. minimumsリストを合計して、下限を取得します。

9

これは貪欲なアプローチになります:

  1. 次数n X mの「スコア」行列を計算します。score[i] [j]は、位置(i、j)が爆撃された場合の行列内のポイントの合計控除です。(ポイントの最大スコアは9で、最小スコアは0です)

  2. 行を賢く移動して、スコアが最も高い最初の位置を見つけて選択します((i、j)と言います)。

  3. 爆弾(i、j)。爆弾数を増やします。

  4. 元の行列のすべての要素がゼロでない場合は、1に進みます。

しかし、これが最適な解決策であるかどうかは疑問です。

編集:

上で投稿した貪欲なアプローチは機能しますが、おそらく最適なソリューションを提供しません。したがって、DPの要素をいくつか追加する必要があると考えました。

いつでも、最高の「スコア」(score [i] [j] =(i、j)が爆撃された場合のポイントの合計控除)を持つポジションの1つをターゲットにする必要があることに同意できると思います。この仮定から始めて、ここに新しいアプローチがあります:

NumOfBombs(M):(必要な爆撃の最小数を返します)

  1. 次数n X mの行列Mが与えられます。Mのすべての要素がゼロの場合、0を返します。

  2. 「スコア」行列Mを計算します。

    k個の異なる位置P1、P2、... Pk(1 <= k <= n * m)を、最高のスコアを持つMの位置とします。

  3. return(1 + min(NumOfBombs(M1)、NumOfBombs(M2)、...、NumOfBombs(Mk)))

    ここで、M1、M2、...、Mkは、それぞれP1、P2、...、Pkの位置を爆撃した場合の結果の行列です。

また、これに加えてポジションの順序を変更したい場合は、「min」の結果を追跡する必要があります。


3
スコアを現在の値の合計に設定すると、より良い結果が得られるのでしょうか。それは基本的により効率的に地面を平らにするでしょう。
ユージーン

@ユージーン:非常に興味深い点。私はあなたの方法がより良い結果を生み出すべきではない理由を考えることはできません...
SidR

@Eugene:おそらく、周辺の現在の値の合計を「優先度」の測定に使用できますか?Nukeの最高得点と優先順位が最も高いノード...
シドル

この回答を読んでみてください。私が投稿した2番目の回答に似ていると思います(おそらく私の回答でもう少し詳しく説明しています)。私はそれが思うだろう最高のスコアを持つ単一のスペースが常に存在した場合は、すべての爆撃が最大可能影響を与えていることを保証することと思いますので、最適なもの。爆撃の順序は重要ではないので、各ステップで最高の爆弾を使用するのが最適です。しかし、「最良」の同点が存在する可能性があるため、おそらく最適なソリューションでは、同点がある場合は、両方をバックトラックして試す必要があります。
Tim Goodman

1
@ユージーン、多分私はあなたをフォローしていません。最大の削減と残りのすべての値の最小合計の違いは何ですか?残りの値の合計(爆撃後)は、現在の合計値からそのスペースへの爆撃による削減量を引いたものです。これらは同等ではありませんか?
Tim Goodman

8

あなたの新しい問題は、行全体で非減少の値で、解決するのはとても簡単です。

左側の列に最大の数値が含まれていることを確認します。したがって、最適なソリューションでは、まずこの列をゼロに減らす必要があります。したがって、この列に対して1次元爆撃を実行して、その列のすべての要素をゼロに減らすことができます。爆弾を2列目に落下させ、最大のダメージを与えます。ここには1Dのケースに関する投稿がたくさんあると思いますので、そのケースをスキップしても安心です。(あなたが私にそれを説明して欲しいのなら、私はできます。)プロパティが減少しているため、左端の3つの列はすべて0に削減されます。ただし、左側の列をゼロにする必要があるため、ここでは爆弾の最小数を使用します。

次に、左の列がゼロになると、ゼロになっている左端の3つの列をトリムして、縮小された行列で繰り返します。各段階で、使用できる爆弾の数は証明できるほど少ないため、これは最適な解決策を提供する必要があります。


わかった。私も似たような考えを思いました。:S次回はもっと注意深く読みます。しかし、そのおかげで多くの人々が「解決するのに良い」問題を抱えています。
abc 2013年

4

分岐限定法を使用したMathematica整数線形計画法

すでに述べたように、この問題は整数線形計画法(NP-Hard)を使用して解決できます。MathematicaにはすでにILPが組み込まれています。"To solve an integer linear programming problem Mathematica first solves the equational constraints, reducing the problem to one containing inequality constraints only. Then it uses lattice reduction techniques to put the inequality system in a simpler form. Finally, it solves the simplified optimization problem using a branch-and-bound method."[ Mathematicaの制約付き最適化チュートリアルを参照してください..]

MathematicaのILPライブラリを利用する次のコードを書きました。驚くほど速いです。

solveMatrixBombProblem[problem_, r_, c_] := 
 Module[{}, 
  bombEffect[x_, y_, m_, n_] := 
   Table[If[(i == x || i == x - 1 || i == x + 1) && (j == y || 
        j == y - 1 || j == y + 1), 1, 0], {i, 1, m}, {j, 1, n}];
  bombMatrix[m_, n_] := 
   Transpose[
    Table[Table[
      Part[bombEffect[(i - Mod[i, n])/n + 1, Mod[i, n] + 1, m, 
        n], (j - Mod[j, n])/n + 1, Mod[j, n] + 1], {j, 0, 
       m*n - 1}], {i, 0, m*n - 1}]];
  X := x /@ Range[c*r];
  sol = Minimize[{Total[X], 
     And @@ Thread[bombMatrix[r, c].X >= problem] && 
      And @@ Thread[X >= 0] && Total[X] <= 10^100 && 
      Element[X, Integers]}, X];
  Print["Minimum required bombs = ", sol[[1]]];
  Print["A possible solution = ", 
   MatrixForm[
    Table[x[c*i + j + 1] /. sol[[2]], {i, 0, r - 1}, {j, 0, 
      c - 1}]]];]

問題で提供されている例の場合:

solveMatrixBombProblem[{2, 3, 4, 7, 1, 1, 5, 2, 6, 2, 4, 3, 4, 2, 1, 2, 1, 2, 4, 1, 3, 1, 3, 4, 1, 2, 1, 4, 3, 2, 6, 9, 1, 6, 4}, 7, 5]

アウトプット

ここに画像の説明を入力してください

貪欲なアルゴリズムでこれを読んでいる人のために

次の10x10の問題でコードを試してください。

5   20  7   1   9   8   19  16  11  3  
17  8   15  17  12  4   5   16  8   18  
4   19  12  11  9   7   4   15  14  6  
17  20  4   9   19  8   17  2   10  8  
3   9   10  13  8   9   12  12  6   18  
16  16  2   10  7   12  17  11  4   15  
11  1   15  1   5   11  3   12  8   3  
7   11  16  19  17  11  20  2   5   19  
5   18  2   17  7   14  19  11  1   6  
13  20  8   4   15  10  19  5   11  12

ここではコンマ区切りです:

5, 20, 7, 1, 9, 8, 19, 16, 11, 3, 17, 8, 15, 17, 12, 4, 5, 16, 8, 18, 4, 19, 12, 11, 9, 7, 4, 15, 14, 6, 17, 20, 4, 9, 19, 8, 17, 2, 10, 8, 3, 9, 10, 13, 8, 9, 12, 12, 6, 18, 16, 16, 2, 10, 7, 12, 17, 11, 4, 15, 11, 1, 15, 1, 5, 11, 3, 12, 8, 3, 7, 11, 16, 19, 17, 11, 20, 2, 5, 19, 5, 18, 2, 17, 7, 14, 19, 11, 1, 6, 13, 20, 8, 4, 15, 10, 19, 5, 11, 12

この問題の場合、私の解決策には208個の爆弾が含まれています。これが可能な解決策です(約12秒で解決できました)。

ここに画像の説明を入力してください

Mathematicaが生成している結果をテストする方法として、貪欲なアルゴリズムがもっとうまくできるかどうかを見てください。


:私はこの答えを219でそれを行うことができましたstackoverflow.com/questions/15300149/bomb-dropping-algorithm/...
アンソニー・クイーン・

3

問題を線形副問題に変換する必要はありません。

代わりに、単純な貪欲なヒューリスティックを使用します。つまり、最大のものから始めて、コーナー爆撃します。

与えられた例には、4つの角{2、1、6、4}があります。各コーナーでは、セルを対角線に対角線で爆撃するよりも良い動きはないので、最初の2 + 1 + 6 + 4 = 13の爆撃がこれらの対角線セルにある必要があることがわかります。爆撃を行った後、新しいマトリックスが残ります。

2 3 4 7 1      0 1 1 6 0      0 1 1 6 0     1 1 6 0     0 0 5     0 0 0 
1 5 2 6 2      0 3 0 5 1      0 3 0 5 1  => 1 0 4 0  => 0 0 3  => 0 0 0  
4 3 4 2 1      2 1 1 1 0      2 1 1 1 0     0 0 0 0     0 0 0     0 0 3  
2 1 2 4 1  =>  2 1 2 4 1  =>  2 1 2 4 1     0 0 3 0     0 0 3      
3 1 3 4 1      0 0 0 0 0      0 0 0 0 0 
2 1 4 3 2      0 0 0 0 0      0 0 0 0 0 
6 9 1 6 4      0 3 0 2 0      0 0 0 0 0 

最初の13回の爆撃の後、ヒューリスティックを使用して3回の爆撃によって3 0 2を排除します。これで、4行目に2つの新しいコーナー{2、1}ができました。私たちはそれらを爆撃し、さらに3回爆撃します。マトリックスを4 x 4に削減しました。左上に1つのコーナーがあります。私たちはそれを爆撃します。これで、2つのコーナーが残っています、{5、3}。5が最大のコーナーなので、最初に5爆撃し、最後に3を他のコーナーに爆撃します。合計は13 + 3 + 3 + 1 + 5 + 3 = 28です。


1
コーナーを爆撃した後、あなたは一般的に何をしているのか分かりません
RiaD 2013年

コーナーを爆撃することは、コーナーから斜め内側に爆撃することよりも効果的ではありません。
psr 2013年

1
psrあなたは私のポストを誤解しています、私はコーナーから斜めに爆撃しています、ポストを読み直します
タイラー・ダーデン

11
@TylerDurden:これは、行列が小さいためにのみ機能します。大きなマトリックスでは、コーナーを爆撃した後は、通常、エッジをカットできなくなります。
リー・ライアン

3

これは、位置の「迷路」を通る最短経路(一連の爆撃)の幅広探索を行います。いいえ、申し訳ありませんが、より高速なアルゴリズムがないことを証明することはできません。

#!/usr/bin/env python

M = ((1,2,3,4),
     (2,3,4,5),
     (5,2,7,4),
     (2,3,5,8))

def eachPossibleMove(m):
  for y in range(1, len(m)-1):
    for x in range(1, len(m[0])-1):
      if (0 == m[y-1][x-1] == m[y-1][x] == m[y-1][x+1] ==
               m[y][x-1]   == m[y][x]   == m[y][x+1] ==
               m[y+1][x-1] == m[y+1][x] == m[y+1][x+1]):
        continue
      yield x, y

def bomb(m, (mx, my)):
  return tuple(tuple(max(0, m[y][x]-1)
      if mx-1 <= x <= mx+1 and my-1 <= y <= my+1
      else m[y][x]
      for x in range(len(m[y])))
    for y in range(len(m)))

def findFirstSolution(m, path=[]):
#  print path
#  print m
  if sum(map(sum, m)) == 0:  # empty?
    return path
  for move in eachPossibleMove(m):
    return findFirstSolution(bomb(m, move), path + [ move ])

def findShortestSolution(m):
  black = {}
  nextWhite = { m: [] }
  while nextWhite:
    white = nextWhite
    nextWhite = {}
    for position, path in white.iteritems():
      for move in eachPossibleMove(position):
        nextPosition = bomb(position, move)
        nextPath = path + [ move ]
        if sum(map(sum, nextPosition)) == 0:  # empty?
          return nextPath
        if nextPosition in black or nextPosition in white:
          continue  # ignore, found that one before
        nextWhite[nextPosition] = nextPath

def main(argv):
  if argv[1] == 'first':
    print findFirstSolution(M)
  elif argv[1] == 'shortest':
    print findShortestSolution(M)
  else:
    raise NotImplementedError(argv[1])

if __name__ == '__main__':
  import sys
  sys.exit(main(sys.argv))

1
このアルゴリズム、最も少ない移動数検出しますが、非常に長い時間がかかる可能性があります。与えられたデータセットでこれを実行しましたか?これは、他のアルゴリズムと比較するためのベースラインを提供します。
Ryan Amos

1
指定されたマトリックスの5x4のサブセットは約2秒で解決され、5x5はすでに2分以上かかりました。私はまだこれ以上試していません;-)はい、このアルゴリズムは元のタスク以外には最適化されていません。最短の解決策を見つけてください。
Alfe

2
これは、指数関数的な複雑さの美しさです。
Ryan Amos

3

ここでは線形計画法のアプローチが非常に役立つようです。

ましょうPのmがXN位置の値を持つ行列です。

ポジションのマトリックス

今定義でき爆弾行列B(x、y)はm×n個を用いて、1≤X≤M 1≤Y≤N以下のように

爆弾行列

そのような方法で

爆弾行列の位置の値

例えば:

B(3、3)

だから私たちはマトリックスを探しています B m xn = [ b ij ]をいます。

  1. 爆弾行列の合計として定義できます。

    爆弾行列の和としてのB

    q ijは、位置p ijにドロップする爆弾の数になります

  2. p ij - B IJ 0≤ (私たちのようにそれを言わせて、よりsuccintされるように- B≤0 P

また、Bは合計を最小化する必要があり爆弾の量の合計ます。

先に醜い行列としてBを書くこともできます:

数量の合計の行列としてのB

それ以来 P - B≤0 (意味P≤Bを)我々は下に次のかなりの線形不等式のシステムを持っています:

投下された爆弾の数とポジションの価値の関係

であること q個のMN×1として定義され

量のベクトル

p mn x 1として定義

ベクトルとして分布するPの値

システムがあると言えます システム行列http://latex.codecogs.com/gif.download?S%5Cmathbf%7Bq%7D&space;%5Cge&space;%5Cmathbf%7Bp%7Dの積として表され、以下のことを S mnはX mnシステムを解くために反転する行列。私はそれを自分で拡張しませんでしたが、コードでそれを行うのは簡単なはずです。

今、私たちは次のように述べることができる最小の問題を抱えています

私たちが解決しなければならないシステム

シンプレックスアルゴリズムのようなもので解決するのは簡単で、ほとんど取るに足らないことだと思います(これについてはかなりクールなドキュメントがあります))。しかし、線形プログラミングはほとんど知りません(Courseraでコースを受講しますが、それは将来のことです...)。それを理解しようとするいくつかの頭痛の種がありました。ここをあきらめてください。ある時点で私が何か間違ったことをしたか、それ以上進むことができないかもしれませんが、私はこの道が最終的解決につながると信じてます。とにかく、私はあなたのフィードバックを切望しています。

この素晴らしいサイトがLaTeX式から写真を作成してくれたことに感謝します


あなたの不平等は逆転しないと確信していますか?それはSq> = Pですか?つまり、正方形が爆撃される合計回数は、指定された行列以上になります。
darksky 2013年

1
線形プログラムの変数が整数に制約される場合、それを「整数線形計画法」(IP)と呼びます。継続的なケースとは異なり、IPはNP-Completeです。残念ながら、近似が受け入れられない限り、シンプレックスアルゴリズムは役に立ちません。そして、IPはすでに別の回答で言及されいます
BlueRaja-Danny Pflughoeft 2013年

@ BlueRaja-DannyPflughoeft正解です。"Despite the many crucial applications of this problem, and intense interest by researchers, no efficient algorithm is known for it.254ページを参照してください。整数線形計画法は非常に難しい計算問題です。効率的であることの唯一の希望は、行列Sに関する固有のプロパティを利用することです。結局のところ、それは恣意的ではありません。
darksky 2013年

3

この貪欲な解決策は正しいようです

コメントで指摘されているように、2Dでは失敗します。しかし、多分あなたはそれを改善するかもしれません。

1Dの
場合:2つ以上の数字がある場合は、2番目の数字が悪いわけではないので、左端の数字に向ける必要はありません。だから、あなたがそれをしなければならないので、最初は0ではありませんが、2番目に撃ってください。次のセルに移動します。最後のセルを忘れないでください。

C ++コード:

void bombs(vector<int>& v, int i, int n){
    ans += n;
    v[i] -= n;
    if(i > 0)
        v[i - 1] -= n;
    if(i + 1< v.size())
        v[i + 1] -= n;
}

void solve(vector<int> v){
    int n = v.size();
    for(int i = 0; i < n;++i){
        if(i != n - 1){
            bombs(v, i + 1, v[i]);
        }
        else
            bombs(v, i, v[i])
    }
}

したがって、2Dの場合:
繰り返しますが、最初の行(2番目の行がある場合)で撮影する必要はありません。だから二番目のものに撃ちます。最初の行の1Dタスクを解きます。(nullにする必要があるため)。降りる。最後の行を忘れないでください。


5
反例:"0110","1110","1110"。あなたは、1つのショットが必要なだけで、私はあなたのアルゴリズムは2を使用すると信じて
maniek

2

爆弾の数を最小限に抑えるには、すべての爆弾の影響を最大化する必要があります。これを達成するには、すべてのステップで最適なターゲットを選択する必要があります。それを合計する各ポイントと8つの隣接ポイント-このポイントを爆撃する効率量として使用できます。これにより、爆弾の最適なシーケンスに近くなります。

UPD:ゼロの数も考慮する必要があります。それらを爆撃するのは非効率的だからです。実際、問題はヒットしたゼロの数を最小限に抑えることです。しかし、どのステップがこの目的に近づくのかを知ることはできません。問題はNP完全であるという考えに同意します。私は貪欲なアプローチを提案します。それは現実に近い答えを与えるでしょう。


これは最適ではありません。10101010010100
反例

2

爆弾の量を最小限に抑えるには、被害の量を最大化するだけで十分だと思います。そのためには、最も強い力を持つ領域を確認する必要があります。まず、3x3カーネルでフィールドを分析し、合計がどこにあるかを確認しますより強く..そしてそこに爆弾..そして、フィールドがフラットになるまで行います..このフィールドのために答えは28です

var oMatrix = [
[2,3,4,7,1],
[1,5,2,6,2],
[4,3,4,2,1],
[2,1,2,4,1],
[3,1,3,4,1],
[2,1,4,3,2],
[6,9,1,6,4]
]

var nBombs = 0;
do
{
    var bSpacesLeftToBomb = false;
    var nHigh = 0;
    var nCellX = 0;
    var nCellY = 0;
    for(var y = 1 ; y<oMatrix.length-1;y++) 
        for(var x = 1 ; x<oMatrix[y].length-1;x++)  
        {
            var nValue = 0;
            for(var yy = y-1;yy<=y+1;yy++)
                for(var xx = x-1;xx<=x+1;xx++)
                    nValue += oMatrix[yy][xx];

            if(nValue>nHigh)
            {
                nHigh = nValue;
                nCellX = x;
                nCellY = y; 
            }

        }
    if(nHigh>0)
    {
        nBombs++;

        for(var yy = nCellY-1;yy<=nCellY+1;yy++)
        {
            for(var xx = nCellX-1;xx<=nCellX+1;xx++)
            {
                if(oMatrix[yy][xx]<=0)
                    continue;
                oMatrix[yy][xx] = --oMatrix[yy][xx];
            }
        }
        bSpacesLeftToBomb = true;
    }
}
while(bSpacesLeftToBomb);

alert(nBombs+'bombs');

これは、他のいくつかの回答と同じアルゴリズムですが、かなり後になります。
psr 2013年

@psrそれだけではありません。最適ではありません。
Mysticial 2013年

このアルゴリズムは提案されていましたが、コードの投稿や「概念の教授」が見つからなかったため、投稿しました。だから私はそれが議論に役立つかもしれないと思った..しかし..ところで@Mysticialより最適な方法があるというあなたの教授はいますか?
CaldasGSM 2013年

@CaldasGSM心配ありません。元の問題(シーケンス処理なし)は難しいものです。これまで最適に解決する答え1つしかありませんが、それは指数関数的に実行されます。
Mysticial

2

コーナーの優れた特性を一般化するソリューションは次のとおりです。

特定のフィールドの完全なドロップポイントを見つけることができたとしましょう。つまり、そのフィールドの値を減らすための最良の方法です。次に、投下される爆弾の最小数を見つけるために、アルゴリズムの最初のドラフトは次のようになります(コードはルビー実装からコピーアンドペーストされます):

dropped_bomb_count = 0
while there_are_cells_with_non_zero_count_left
  coordinates = choose_a_perfect_drop_point
  drop_bomb(coordinates)
  dropped_bomb_count += 1
end
return dropped_bomb_count

挑戦はchoose_a_perfect_drop_pointです。最初に、完璧なドロップポイントとは何かを定義しましょう。

  • ドロップポイントのためには、(x, y)の値を減少させます(x, y)。他のセルの値も減少する可能性があります。
  • 落ち点Aがため(x, y)であるよりよい落ち点よりもBのための(x, y)それは細胞の適切なスーパーセットの値を減少する場合、Bは減少します。
  • ドロップポイントは、他に優れたドロップポイントがない場合に最大になります。
  • 同じセルのセットを減少させる場合、の2つのドロップポイント(x, y)同等です。
  • のドロップポイント(x, y)は、のすべての最大ドロップポイントと同等であれば完璧です(x, y)

の完全な落下ポイントがある場合、の完全な落下ポイントの1つに爆弾を投下するよりも効果的に(x, y)値を下げることはできません。(x, y)(x, y)

特定のフィールドの完全なドロップポイントは、そのセルのいずれかの完全なドロップポイントです。

以下にいくつかの例を示します。

1 0 1 0 0
0 0 0 0 0
1 0 0 0 0
0 0 0 0 0
0 0 0 0 0

セルの完全なドロップポイント(0, 0)(ゼロベースのインデックス)は(1, 1)です。他のすべてのドロップポイント(1, 1)である、(0, 0)(0, 1)、とは(1, 0)、より少ない細胞を減少させます。

0 0 0 0 0
0 0 0 0 0
0 0 1 0 0
0 0 0 0 0
0 0 0 0 0

細胞のための完全なドロップポイント(2, 2)(ゼロベースのインデックス)は(2, 2)、また、全ての周囲の細胞(1, 1)(1, 2)(1, 3)(2, 1)(2, 3)(3, 1)(3, 2)、および(3, 3)

0 0 0 0 1
0 0 0 0 0
0 0 1 0 0
0 0 0 0 0
0 0 0 0 0

セルのための完璧なドロップポイントが(2, 2)ある(3, 1):それはで値を減少させ(2, 2)、そして内の値(4, 0)。他のすべてのドロップポイントは(2, 2)、1セル減少するため、最大ではありません。の完全なドロップポイント(2, 2)は、の完全なドロップポイントでもあり(4, 0)、フィールドで唯一の完全なドロップポイントです。これは、この分野(爆弾1発)に最適なソリューションをもたらします。

1 0 0 0 0
0 0 0 0 0
0 0 1 0 0
0 0 0 0 0
1 0 0 0 0

そこには完璧なドロップポイントのためではありません(2, 2):両方(1, 1)(1, 3)減少(2, 2)し、別のセルには、(彼らはのための最大のドロップポイントである(2, 2))、しかし、彼らは同じではありません。ただし、(1, 1)は完全なドロップポイントで(0, 0)あり、(1, 3)は完全なドロップポイントです。(0, 4)

完全なドロップポイントの定義と特定の順序のチェックにより、質問の例について次の結果が得られます。

Drop bomb on 1, 1
Drop bomb on 1, 1
Drop bomb on 1, 5
Drop bomb on 1, 5
Drop bomb on 1, 5
Drop bomb on 1, 6
Drop bomb on 1, 2
Drop bomb on 1, 2
Drop bomb on 0, 6
Drop bomb on 0, 6
Drop bomb on 2, 1
Drop bomb on 2, 5
Drop bomb on 2, 5
Drop bomb on 2, 5
Drop bomb on 3, 1
Drop bomb on 3, 0
Drop bomb on 3, 0
Drop bomb on 3, 0
Drop bomb on 3, 0
Drop bomb on 3, 0
Drop bomb on 3, 4
Drop bomb on 3, 4
Drop bomb on 3, 3
Drop bomb on 3, 3
Drop bomb on 3, 6
Drop bomb on 3, 6
Drop bomb on 3, 6
Drop bomb on 4, 6
28

ただし、アルゴリズムは、各ステップの後に少なくとも1つの完全なドロップポイントがある場合にのみ機能します。完全なドロップポイントがない例を作成することができます。

0 1 1 0
1 0 0 1
1 0 0 1
0 1 1 0

これらの場合、アルゴリズムを変更して、完全なドロップポイントの代わりに、最大のドロップポイントの最小の選択肢を持つ座標を選択し、各選択肢の最小値を計算します。上記の場合、値を持つすべてのセルに2つの最大ドロップポイントがあります。たとえば、(0, 1)には最大のドロップポイント(1, 1)とがあり(1, 2)ます。どちらかを選択してから最小値を計算すると、次の結果になります。

Drop bomb on 1, 1
Drop bomb on 2, 2
Drop bomb on 1, 2
Drop bomb on 2, 1
2

これは、上記の貪欲なアルゴリズムとほぼ同じです。
darksky 2013年

まあ、それも貪欲なアルゴリズムですが、コーナーとエッジに焦点を当てるのではなく、次のドロップポイントを選択する方法を定義しました。5x7の正方形の例では、1000x1000のフィールドのコーナーについてはそれほど簡単ではありません。私のアルゴリズムがフィールドをクリアする順序を確認すると、それは外側からではなく、上から下/左から右です。
Tammo Freese 2013

2

ここに別のアイデアがあります:

まず、爆弾を投下することで削減できる数をボードの各スペースに割り当てます。そのため、スペースにゼロ以外の数値がある場合はポイントを取得し、隣接するスペースにゼロ以外の数値がある場合は追加のポイントを取得します。したがって、1000行1000列のグリッドがある場合、100万個のスペースのそれぞれに重みが割り当てられます。

次に、スペースのリストを重量で並べ替え、最も重量の大きいものを爆撃します。これは、いわば、私たちの支出に見合うだけの価値があります。

その後、爆弾の影響を受けるすべてのスペースの重みを更新します。これは、爆撃したスペース、そのすぐ隣のスペース、およびそれらのすぐ隣のスペースになります。言い換えれば、爆撃によってその値が0に減少した可能性のあるスペース、または隣接するスペースの値が0に減少したスペース。

次に、リストスペースを重みで並べ替えます。爆撃によって変更されたスペースの小さなサブセットだけなので、リスト全体を再ソートする必要はなく、リスト内でそれらを移動するだけです。

新しい最高重量のスペースを爆撃し、手順を繰り返します。

これは、すべての爆撃が可能な限り多くのスペースを削減することを保証します(基本的に、すでにゼロになっているスペースにできるだけ当たらない)、したがって、それらは重みで結合できることを除いて、最適です。したがって、トップウェイトのタイがある場合は、バックトラッキングを行う必要があるかもしれません。ただし、重要なのは同点のみで、他の同点は関係ないため、バックトラッキングが多すぎないことを願います。

編集: 以下のMysticialの反例は、重みの関係に関係なく、これが実際に最適であるとは限らないことを示しています。場合によっては、特定のステップで可能な限り重量を減らすと、実際には残りの爆弾が広がりすぎて、最初のステップで少し貪欲な選択をする場合と同じように、2番目のステップの後には累積的な削減を達成できません。結果は爆撃の順番に鈍感であるという考えに多少誤解を招きました。彼ら一連の爆撃を取り、最初から別の順序で再生し、最終的に同じ結果のボードになる可能性があるため、順序に影響されません。しかし、それはあなたがそれぞれの爆撃を独立して考えることができるということには従いません。または、少なくとも、各爆撃は、それがその後の爆撃のために理事会をどれだけうまく設定するかを考慮した方法で考慮されなければなりません。


それでも多くのバックトラックが行われます。最初はフィールドのゼロがほとんどないため、ほとんどのセルの重みはすべて9になります。
リーライアン

可能な重みには大きな範囲がないため(0から9のみ)、そうです。
Tim Goodman

私はまだバックトラックがどれほど必要かを100%確信していません...貪欲爆撃の1つの選択が貪欲爆撃の別の選択よりも劣るグリッドを構築することは有益かもしれません。どちらが良いかを予測する一貫した方法があるかもしれません。
Tim Goodman

実際、パニック大佐が彼の答えでこれをしたと思います。貪欲な選択のほうが他の選択よりも優れている理由は、残りの数をさらに広げておくためです。
Tim Goodman

3
10101010010100このアプローチが最適ではないことを証明する反例かもしれません。このアプローチは、それが2で行うことができます。3.必要です
Mysticial

1

まあ、ボードの位置を1、2、...、nx mと番号付けするとします。爆弾投下のシーケンスは、このセット内の一連の番号で表すことができ、番号を繰り返すことができます。ただし、爆弾を投下する順序に関係なく、ボードへの影響は同じであるため、実際に爆弾を投下する場合は、nxm番号のリストとして表すことができます。最初の番号は、位置1に投下された爆弾の数を表します。 、2番目の数字はポジション2に投下された爆弾の数を表します。このnxm番号のリストを「キー」と呼びましょう。

最初に1爆弾の落下から生じるすべてのボード状態を計算し、次にこれらを使用して2爆弾の落下から生じるすべてのボード状態を計算し、すべてゼロになるまで続けます。しかし、各ステップでは、上で定義したキーを使用して状態をキャッシュするため、これらの結果を次のステップ(「動的プログラミング」アプローチ)の計算に使用できます。

ただし、n、m、およびグリッド内の数のサイズによっては、このアプローチのメモリ要件が過剰になる場合があります。N + 1のすべての結果を計算したら、N爆弾のドロップのすべての結果を破棄することができるので、そこにいくつかの節約があります。そしてもちろん、何もキャッシュに入れることはできませんが時間がかかることになります。動的プログラミングのアプローチは、メモリを速度と引き換えに使用します。


1
(私があなたを正しく理解していれば)それが可能であることは間違いありません。n = m。(10 ^ 6)^ 2 intセルへの10 ^ 6 intポインターが必要です。テーブルのキーと同じ数のボードがあります。10 ^ 12疑い私は32ビットマシンでそんなに割り当てることができます。
abc 2013年

ええ、私はちょうどボードが1000 x 1000までであるというコメントを見たところです。つまり、各ボードの状態は100万イントになり、各ポジションに投下された爆弾の数は100万イントになります。したがって、保存するボードごとに200万整数が必要であり、可能なボードはたくさんあります...
Tim Goodman

別のアプローチを使用する2番目の回答を追加しました。
Tim Goodman

1
うん。強引なアプローチのようなものですが、大きなボードにはあまり実用的ではないと思います。
Tim Goodman

@Kostek、なぜそんなに低い見積もりですか?これは、k ^(m * n)メモリに似ています。kは、ボードが最初に満たされる数の制限です。
Rotsor 2013年

1

ボードをきれいにするための絶対最適解が必要な場合は、古典的なバックトラッキングを使用する必要がありますが、行列が非常に大きい場合、最適解を見つけるには時間がかかります。「可能な」最適解が必要な場合は、貪欲アルゴリズムを使用できます。 、あなたがアルゴリズムを書くのに助けが必要な場合、私はあなたを助けることができます

それが最善の方法だと考えてください。そこに爆弾をドロップして削除したポイントを格納する別のマトリックスを作成し、最大ポイントのセルを選択してそこに爆弾をドロップし、ポイントマトリックスを更新して続行します。例:

2 3 5 -> (2+(1*3)) (3+(1*5)) (5+(1*3))
1 3 2 -> (1+(1*4)) (3+(1*7)) (2+(1*4))
1 0 2 -> (1+(1*2)) (0+(1*5)) (2+(1*2))

0より大きい値を持つ隣接するセルごとにセル値+1


7
従来のバックトラッキング使用する必要があります。これの証拠はありますか?
Shahbaz 2013年

よく分かりません。私が準備しているコンテストからです(前年度から)。制限は1 <= n、m <= 1000です(大きいかどうかはわかりません)。とにかく正確な答えが必要です(これはCERCコンテストなどに似ています)。時間制限は、与えられていません、答えも、コンテストページのソリューションもありません。
abc 2013年

よく他のすべてのアルゴリズムは可能な最適なソリューションを提供しますが、それらすべてを試す(バックトラック)まで、そのソリューションが最良であるかどうかはわかりません
cosmin.danisor

2
順列ではなく、検索する組み合わせであるため、バックトラッキングを使用する必要はありません。爆弾を投下する順序は重要ではありません
Luka Rahne

その後、貪欲のバリエーションを使用してみることができます。すべてのステップで新しいマトリックスを作成し、すべてのポイントはそのセルの値+ 1の隣のすべてのセルに対して1より大きい> 0この方法は、次の爆弾をドロップする場所をより適切に選択します
cosmin.danisor

1

強引な !

私はそれが効率的ではないことを知っていますが、より高速なアルゴリズムを見つけた場合でも、いつでもこの結果に対してテストして、それがどれほど正確かを知ることができます。

次のような再帰を使用します。

void fn(tableState ts, currentlevel cl)
{
  // first check if ts is all zeros yet, if not:
  //
  // do a for loop to go through all cells of ts, 
  // for each cell do a bomb, and then
  // call: 
  // fn(ts, cl + 1);

}

キャッシュすることでこれをより効率的にすることができます。異なる方法で同じ結果になる場合は、同じ手順を繰り返さないでください。

詳しく説明するには:

セル1,3,5を爆撃すると、セル5,3,1を爆撃した場合と同じ結果になる場合は、両方のケースで次のすべての手順を再度実行する必要はありません。1つだけで十分です。テーブルの状態とその結果を使用します。

テーブル統計のハッシュを使用して、高速な比較を行うことができます。


1
  1. 境界線を爆撃しない(正方形に非境界隣接がない場合を除く)
  2. ゼロコーナー。
  3. コーナーをゼロにするには、コーナーの値を1スクエア対角線上にドロップします(ボーダー以外の隣接セルのみ)。
  4. これにより、新しいコーナーが作成されます。2へ

編集:Kostekがほぼ同じアプローチを提案したことに気づかなかったので、今私はより強い主張をします:クリアするコーナーが常に最外層にあるように選択されている場合、それは最適です。

OPの例では、5以外で2をドロップすると(1 + 1または2として)、5をドロップするとヒットする正方形にヒットすることにはなりません。したがって、5に2をドロップする必要があります(そして、左下に6をドロップする1 ...)

この後、元の1(現在は0)だったものを(左上に)クリアする方法は1つだけです。それは、B3に0をドロップすることです(表記のようにExcelを使用)。等々。

AとEの列全体と1と7の行をすべてクリアした後でのみ、1層深くクリアを開始します。

意図的にクリアされたものだけをクリアすることを検討してください。0の値のコーナーをクリアしてもコストはかかりません。

この方法で投下されたすべての爆弾は投下される必要があり、これによりフィールドがクリアされるため、最適なソリューションです。


良い睡眠の後、私はこれが真実ではないことに気づきました。検討する

  ABCDE    
1 01000
2 10000
3 00000
4 00000

私のアプローチでは、B3とC2に爆弾を投下しますが、B2に投下するだけで十分です。


しかし、これは最適ですか?
Mysticial 2013年

7
新しいコーナーは2つの方法で爆弾になる可能性があります(ほとんどのコーナーポイントに4つすべての値の最低値が含まれている場合)。どれが最適な爆撃ですか?
abc 2013年

私は同様のアプローチについて考えていました。Kostekで説明されているような状況に到達したら、バックトラックを使い始めます...
Karoly Horvath

コーナーは、対角線上の正方形にドロップされる爆弾の最小量を提供します。しかし、それらをゼロにすると、次のボーダータイルに明らかな最適点があるとは限りません。ただし、検索スペースを減らすには良い方法です。
ユージーン

ヒットボックスの合計数が最大になる新しいコーナーの対角線を選択するのはどうですか?
Maygarden裁判官、2013年

1

これが私の解決策です..時間がないので、まだコードで書きませんが、毎回最適な数の移動を生成するはずです-見つけるのにどれほど効率的かはわかりませんが爆撃するポイント

まず、@ Luka Rahneがコメントの1つで述べたように、爆撃する順序は重要ではなく、組み合わせだけです。

第二に、他の多くの人が述べたように、コーナーよりも多くのポイントに触れるため、コーナーから1対角の対角線を爆撃することが最適です。

これにより、私のバージョンのアルゴリズムの基礎が生成されます。「コーナーからの1オフ」を最初または最後に爆撃できます。問題はありません(理論的には)最初に爆撃します。これにより、後の決定が容易になります(実際)。ほとんどのポイントに影響を与えるポイントを爆撃すると同時に、それらのコーナーを爆撃します。

ポイントオブレジスタンスを、爆撃不能なポイント + 0の最大数を含むボード内のポイントとして定義しましょう周囲

爆撃不能なポイントは、現在見ているボードの現在のスコープに存在しないポイントとして定義できます。

また、スコープを処理する4つの境界を定義します。Top= 0、Left = 0、Bottom = k、right = jです。(開始する値)

最後に、最適な爆弾を、抵抗ポイントに隣接し、(1)最高評価ポイントと(2)可能な最大数のポイントに接触しているポイントに投下される爆弾と定義します。

アプローチに関しては、外部から作業していることは明らかです。同時に4つの「爆撃機」で作業することができます。

抵抗の最初のポイントは明らかに私たちのコーナーです。「範囲外」のポイントは爆撃できません(各コーナーのスコープ外に5ポイントあります)。したがって、最初に角から1つずつ斜めにポイントを爆撃します。

アルゴリズム:

  1. 4つの最適な爆弾ポイントを見つけます。
  2. 爆弾ポイントが2つの境界(つまり、コーナー)に接触している抵抗ポイントを爆撃している場合、そのポイントまで爆撃します。それ以外の場合は、最適爆弾ポイントに接触している抵抗ポイントの1つが0になるまで爆撃します。
  3. 各境界:if(sum(bound)== 0)事前境界

TOP = BOTTOMおよびLEFT = RIGHTまで繰り返します

後で実際のコードを書いてみる


1

状態空間計画を使用できます。たとえば、A *(またはそのバリアントの1つ)を次のf = g + hようなヒューリスティックと組み合わせて使用​​します。

  • g:これまでに投下された爆弾の数
  • h:グリッドのすべての値を9で割った合計(これは最良の結果です。つまり、許容できるヒューリスティックが存在します)

1

私も28手を取得しました。私は次のベストムーブに2つのテストを使用しました。第2に、合計が等しい場合、次のように定義される最大密度を生成する移動:

number-of-zeros / number-of-groups-of-zeros

これがHaskellです。「ソルブボード」は、エンジンのソリューションを示しています。「メイン」と入力してゲームをプレイしてから、目標ポイントを入力するか、推奨の「最高」を入力するか、「終了」で終了します。

出力:
*メイン>ソルブボード
[(4,4)、(3,6)、(3,3)、(2,2)、(2,2)、(4,6)、(4,6)、 (2,6)、(3,2)、(4,2)、(2,6)、(3,3)、(4,3)、(2,6)、(4,2)、(4 、6)、(4、6)、(3、6)、(2、6)、(2、6)、(2、4)、(2、4)、(2、6)、(3、6 )、(4,2)、(4,2)、(4,2)、(4,2)]

import Data.List
import Data.List.Split
import Data.Ord
import Data.Function(on)

board = [2,3,4,7,1,
         1,5,2,6,2,
         4,3,4,2,1,
         2,1,2,4,1,
         3,1,3,4,1,
         2,1,4,3,2,
         6,9,1,6,4]

n = 5
m = 7

updateBoard board pt =
  let x = fst pt
      y = snd pt
      precedingLines = replicate ((y-2) * n) 0
      bomb = concat $ replicate (if y == 1
                                    then 2
                                    else min 3 (m+2-y)) (replicate (x-2) 0 
                                                         ++ (if x == 1 
                                                                then [1,1]
                                                                else replicate (min 3 (n+2-x)) 1)
                                                                ++ replicate (n-(x+1)) 0)
  in zipWith (\a b -> max 0 (a-b)) board (precedingLines ++ bomb ++ repeat 0)

showBoard board = 
  let top = "   " ++ (concat $ map (\x -> show x ++ ".") [1..n]) ++ "\n"
      chunks = chunksOf n board
  in putStrLn (top ++ showBoard' chunks "" 1)
       where showBoard' []     str count = str
             showBoard' (x:xs) str count =
               showBoard' xs (str ++ show count ++ "." ++ show x ++ "\n") (count+1)

instances _ [] = 0
instances x (y:ys)
  | x == y    = 1 + instances x ys
  | otherwise = instances x ys

density a = 
  let numZeros = instances 0 a
      groupsOfZeros = filter (\x -> head x == 0) (group a)
  in if null groupsOfZeros then 0 else numZeros / fromIntegral (length groupsOfZeros)

boardDensity board = sum (map density (chunksOf n board))

moves = [(a,b) | a <- [2..n-1], b <- [2..m-1]]               

bestMove board = 
  let lowestSumMoves = take 1 $ groupBy ((==) `on` snd) 
                              $ sortBy (comparing snd) (map (\x -> (x, sum $ updateBoard board x)) (moves))
  in if null lowestSumMoves
        then (0,0)
        else let lowestSumMoves' = map (\x -> fst x) (head lowestSumMoves) 
             in fst $ head $ reverse $ sortBy (comparing snd) 
                (map (\x -> (x, boardDensity $ updateBoard board x)) (lowestSumMoves'))   

solve board = solve' board [] where
  solve' board result
    | sum board == 0 = result
    | otherwise      = 
        let best = bestMove board 
        in solve' (updateBoard board best) (result ++ [best])

main :: IO ()
main = mainLoop board where
  mainLoop board = do 
    putStrLn ""
    showBoard board
    putStr "Pt: "
    a <- getLine
    case a of 
      "quit"    -> do putStrLn ""
                      return ()
      "best"    -> do putStrLn (show $ bestMove board)
                      mainLoop board
      otherwise -> let ws = splitOn "," a
                       pt = (read (head ws), read (last ws))
                   in do mainLoop (updateBoard board pt)

1

ここには、2つの部分に一致しない部分構造があるようです。次の例を考えてみます。

0010000
1000100
0000001
1000000
0000001
1000100
0010000

このケースの最適解はサイズ5です。これは、エッジによる9サイクルの頂点の最小カバーのサイズであるためです。

このケースは、特に、数人が投稿した線形計画緩和が正確ではなく、機能しないこと、およびその他すべての悪いことを示しています。「平面キュービックグラフの頂点をできるだけ少ないエッジでカバーする」ことを問題に減らすことができると確信しています。これは、貪欲/山登りのソリューションが機能するかどうか疑わしくなります。

最悪の場合、多項式時間でこれを解決する方法がわかりません。私が見ない非常に巧妙なバイナリ検索およびDPソリューションがあるかもしれません。

編集:コンテスト(http://deadline24.pl)は言語に依存しないことがわかります。それらはあなたにたくさんの入力ファイルを送り、あなたはそれらに出力を送ります。したがって、最悪の場合の多項式時間で実行されるものは必要ありません。特に、入力確認できます

入力には小さなケースがたくさんあります。次に、10x1000ケース、100x100ケース、1000x1000ケースがあります。3つの大きなケースはすべて非常に行儀が良いです。水平方向に隣接するエントリは、通常同じ値です。比較的頑丈なマシンでは、CPLEXを使用して総当たりで数分ですべてのケースを解決できます。1000x1000で運が良かった。LP緩和には、たまたま不可欠な最適解があります。私のソリューション.ansは、テストデータバンドルで提供されるファイルに同意します。

入力の構造は、私が見た場合よりもはるかに直接的な方法で使用できると思います。最初の行、または2つ、または3つを繰り返して、何もなくなるまで繰り返すことができるようです。(1000x1000では、すべての行が増加していないように見えますか?それが、「パートB」がどこから来たのでしょうか?)


うん。時々私は単にテキストの「無関係な」部分をスキップします。ちょっと考えてみてください。今回は、基本的にレベルを地獄のように簡単からハードに変更します。一方、答えがパーセンテージポイントでない場合は、5時間で簡単に実行できるアルゴリズムが必要だと思います。私が見つけたすべてはあまりにも複雑すぎました。それから、誰かが起源について尋ねたとき、私はそれをより注意深く読んだ:)
abc

多くの人が考えて良い問題を抱えていることを感謝しますが、多項式の時間でそれができるのではないかと疑います。1つの単純な制約が、タスクのレベルを簡単から不可能に変更するのはおかしいです。
abc

@Kostek:わからなかったらごめんなさい。聴衆にふさわしいレベルで説明するのはかなり苦手です。:)行方不明でしたか?
tmyklebu 2013年

1

私は最善のヒューリスティックを使用して爆撃キャンペーンを計算せずに実際の数を計算する方法を考えることができず、妥当な結果が得られることを願っています。

したがって、私の方法は、各セルの爆撃効率メトリックを計算し、最高値のセルを爆撃することです。すべてを平坦化するまでプロセスを繰り返します。単純な潜在的な損傷(つまり、0から9までのスコア)をメトリックとして使用することを提唱している人もいますが、高い値のセルを叩いて損傷の重複を利用しないと不十分です。私は計算しcell value - sum of all neighbouring cells、正の値を0にリセットし、負の値の絶対値を使用します。直感的に、この測定基準は、それらを直接叩くのではなく、数が多いセルの損傷の重複を最大化するのに役立つ選択を行う必要があります。

以下のコードは、28個の爆弾のテストフィールドの完全破壊に到達します(メトリックとして潜在的な損傷を使用すると、31が生成されます!)。

using System;
using System.Collections.Generic;
using System.Linq;

namespace StackOverflow
{
  internal class Program
  {
    // store the battle field as flat array + dimensions
    private static int _width = 5;
    private static int _length = 7;
    private static int[] _field = new int[] {
        2, 3, 4, 7, 1,
        1, 5, 2, 6, 2,
        4, 3, 4, 2, 1,
        2, 1, 2, 4, 1,
        3, 1, 3, 4, 1,
        2, 1, 4, 3, 2,
        6, 9, 1, 6, 4
    };
    // this will store the devastation metric
    private static int[] _metric;

    // do the work
    private static void Main(string[] args)
    {
        int count = 0;

        while (_field.Sum() > 0)
        {
            Console.Out.WriteLine("Round {0}:", ++count);
            GetBlastPotential();
            int cell_to_bomb = FindBestBombingSite();
            PrintField(cell_to_bomb);
            Bomb(cell_to_bomb);
        }
        Console.Out.WriteLine("Done in {0} rounds", count);
    } 

    // convert 2D position to 1D index
    private static int Get1DCoord(int x, int y)
    {
        if ((x < 0) || (y < 0) || (x >= _width) || (y >= _length)) return -1;
        else
        {
            return (y * _width) + x;
        }
    }

    // Convert 1D index to 2D position
    private static void Get2DCoord(int n, out int x, out int y)
    {
        if ((n < 0) || (n >= _field.Length))
        {
            x = -1;
            y = -1;
        }
        else
        {
            x = n % _width;
            y = n / _width;
        }
    }

    // Compute a list of 1D indices for a cell neighbours
    private static List<int> GetNeighbours(int cell)
    {
        List<int> neighbours = new List<int>();
        int x, y;
        Get2DCoord(cell, out x, out y);
        if ((x >= 0) && (y >= 0))
        {
            List<int> tmp = new List<int>();
            tmp.Add(Get1DCoord(x - 1, y - 1));
            tmp.Add(Get1DCoord(x - 1, y));
            tmp.Add(Get1DCoord(x - 1, y + 1));
            tmp.Add(Get1DCoord(x, y - 1));
            tmp.Add(Get1DCoord(x, y + 1));
            tmp.Add(Get1DCoord(x + 1, y - 1));
            tmp.Add(Get1DCoord(x + 1, y));
            tmp.Add(Get1DCoord(x + 1, y + 1));

            // eliminate invalid coords - i.e. stuff past the edges
            foreach (int c in tmp) if (c >= 0) neighbours.Add(c);
        }
        return neighbours;
    }

    // Compute the devastation metric for each cell
    // Represent the Value of the cell minus the sum of all its neighbours
    private static void GetBlastPotential()
    {
        _metric = new int[_field.Length];
        for (int i = 0; i < _field.Length; i++)
        {
            _metric[i] = _field[i];
            List<int> neighbours = GetNeighbours(i);
            if (neighbours != null)
            {
                foreach (int j in neighbours) _metric[i] -= _field[j];
            }
        }
        for (int i = 0; i < _metric.Length; i++)
        {
            _metric[i] = (_metric[i] < 0) ? Math.Abs(_metric[i]) : 0;
        }
    }

    //// Compute the simple expected damage a bomb would score
    //private static void GetBlastPotential()
    //{
    //    _metric = new int[_field.Length];
    //    for (int i = 0; i < _field.Length; i++)
    //    {
    //        _metric[i] = (_field[i] > 0) ? 1 : 0;
    //        List<int> neighbours = GetNeighbours(i);
    //        if (neighbours != null)
    //        {
    //            foreach (int j in neighbours) _metric[i] += (_field[j] > 0) ? 1 : 0;
    //        }
    //    }            
    //}

    // Update the battle field upon dropping a bomb
    private static void Bomb(int cell)
    {
        List<int> neighbours = GetNeighbours(cell);
        foreach (int i in neighbours)
        {
            if (_field[i] > 0) _field[i]--;
        }
    }

    // Find the best bombing site - just return index of local maxima
    private static int FindBestBombingSite()
    {
        int max_idx = 0;
        int max_val = int.MinValue;
        for (int i = 0; i < _metric.Length; i++)
        {
            if (_metric[i] > max_val)
            {
                max_val = _metric[i];
                max_idx = i;
            }
        }
        return max_idx;
    }

    // Display the battle field on the console
    private static void PrintField(int cell)
    {
        for (int x = 0; x < _width; x++)
        {
            for (int y = 0; y < _length; y++)
            {
                int c = Get1DCoord(x, y);
                if (c == cell)
                    Console.Out.Write(string.Format("[{0}]", _field[c]).PadLeft(4));
                else
                    Console.Out.Write(string.Format(" {0} ", _field[c]).PadLeft(4));
            }
            Console.Out.Write(" || ");
            for (int y = 0; y < _length; y++)
            {
                int c = Get1DCoord(x, y);
                if (c == cell)
                    Console.Out.Write(string.Format("[{0}]", _metric[c]).PadLeft(4));
                else
                    Console.Out.Write(string.Format(" {0} ", _metric[c]).PadLeft(4));
            }
            Console.Out.WriteLine();
        }
        Console.Out.WriteLine();
    }           
  }
}

結果の爆撃パターンは次のように出力されます(左側のフィールド値、右側のメトリック)。

Round 1:
  2   1   4   2   3   2   6  ||   7  16   8  10   4  18   6
  3   5   3   1   1   1   9  ||  11  18  18  21  17  28   5
  4  [2]  4   2   3   4   1  ||  19 [32] 21  20  17  24  22
  7   6   2   4   4   3   6  ||   8  17  20  14  16  22   8
  1   2   1   1   1   2   4  ||  14  15  14  11  13  16   7

Round 2:
  2   1   4   2   3   2   6  ||   5  13   6   9   4  18   6
  2   4   2   1   1  [1]  9  ||  10  15  17  19  17 [28]  5
  3   2   3   2   3   4   1  ||  16  24  18  17  17  24  22
  6   5   1   4   4   3   6  ||   7  14  19  12  16  22   8
  1   2   1   1   1   2   4  ||  12  12  12  10  13  16   7

Round 3:
  2   1   4   2   2   1   5  ||   5  13   6   7   3  15   5
  2   4   2   1   0   1   8  ||  10  15  17  16  14  20   2
  3  [2]  3   2   2   3   0  ||  16 [24] 18  15  16  21  21
  6   5   1   4   4   3   6  ||   7  14  19  11  14  19   6
  1   2   1   1   1   2   4  ||  12  12  12  10  13  16   7

Round 4:
  2   1   4   2   2   1   5  ||   3  10   4   6   3  15   5
  1   3   1   1   0   1   8  ||   9  12  16  14  14  20   2
  2   2   2   2   2  [3]  0  ||  13  16  15  12  16 [21] 21
  5   4   0   4   4   3   6  ||   6  11  18   9  14  19   6
  1   2   1   1   1   2   4  ||  10   9  10   9  13  16   7

Round 5:
  2   1   4   2   2   1   5  ||   3  10   4   6   2  13   3
  1   3   1   1   0  [0]  7  ||   9  12  16  13  12 [19]  2
  2   2   2   2   1   3   0  ||  13  16  15  10  14  15  17
  5   4   0   4   3   2   5  ||   6  11  18   7  13  17   6
  1   2   1   1   1   2   4  ||  10   9  10   8  11  13   5

Round 6:
  2   1   4   2   1   0   4  ||   3  10   4   5   2  11   2
  1   3   1   1   0   0   6  ||   9  12  16  11   8  13   0
  2   2   2   2   0   2   0  ||  13  16  15   9  14  14  15
  5   4  [0]  4   3   2   5  ||   6  11 [18]  6  11  15   5
  1   2   1   1   1   2   4  ||  10   9  10   8  11  13   5

Round 7:
  2   1   4   2   1   0   4  ||   3  10   4   5   2  11   2
  1   3   1   1   0   0   6  ||   8  10  13   9   7  13   0
  2  [1]  1   1   0   2   0  ||  11 [15] 12   8  12  14  15
  5   3   0   3   3   2   5  ||   3   8  10   3   8  15   5
  1   1   0   0   1   2   4  ||   8   8   7   7   9  13   5

Round 8:
  2   1   4   2   1   0   4  ||   1   7   2   4   2  11   2
  0   2   0   1   0   0   6  ||   7   7  12   7   7  13   0
  1   1   0   1   0   2   0  ||   8   8  10   6  12  14  15
  4   2   0   3   3  [2]  5  ||   2   6   8   2   8 [15]  5
  1   1   0   0   1   2   4  ||   6   6   6   7   9  13   5

Round 9:
  2   1   4   2   1   0   4  ||   1   7   2   4   2  11   2
  0   2   0   1   0   0   6  ||   7   7  12   7   6  12   0
  1   1   0   1   0  [1]  0  ||   8   8  10   5  10 [13] 13
  4   2   0   3   2   2   4  ||   2   6   8   0   6   9   3
  1   1   0   0   0   1   3  ||   6   6   6   5   8  10   4

Round 10:
  2   1   4   2   1   0   4  ||   1   7   2   4   2  10   1
  0   2  [0]  1   0   0   5  ||   7   7 [12]  7   6  11   0
  1   1   0   1   0   1   0  ||   8   8  10   4   8   9  10
  4   2   0   3   1   1   3  ||   2   6   8   0   6   8   3
  1   1   0   0   0   1   3  ||   6   6   6   4   6   7   2

Round 11:
  2   0   3   1   1   0   4  ||   0   6   0   3   0  10   1
  0   1   0   0   0  [0]  5  ||   4   5   5   5   3 [11]  0
  1   0   0   0   0   1   0  ||   6   8   6   4   6   9  10
  4   2   0   3   1   1   3  ||   1   5   6   0   5   8   3
  1   1   0   0   0   1   3  ||   6   6   6   4   6   7   2

Round 12:
  2   0   3   1   0   0   3  ||   0   6   0   2   1   7   1
  0   1   0   0   0   0   4  ||   4   5   5   4   1   7   0
  1   0   0   0   0  [0]  0  ||   6   8   6   4   5  [9]  8
  4   2   0   3   1   1   3  ||   1   5   6   0   4   7   2
  1   1   0   0   0   1   3  ||   6   6   6   4   6   7   2

Round 13:
  2   0   3   1   0   0   3  ||   0   6   0   2   1   6   0
  0   1   0   0   0   0   3  ||   4   5   5   4   1   6   0
  1  [0]  0   0   0   0   0  ||   6  [8]  6   3   3   5   5
  4   2   0   3   0   0   2  ||   1   5   6   0   4   6   2
  1   1   0   0   0   1   3  ||   6   6   6   3   4   4   0

Round 14:
  2   0   3   1   0  [0]  3  ||   0   5   0   2   1  [6]  0
  0   0   0   0   0   0   3  ||   2   5   4   4   1   6   0
  0   0   0   0   0   0   0  ||   4   4   4   3   3   5   5
  3   1   0   3   0   0   2  ||   0   4   5   0   4   6   2
  1   1   0   0   0   1   3  ||   4   4   5   3   4   4   0

Round 15:
  2   0   3   1   0   0   2  ||   0   5   0   2   1   4   0
  0   0   0   0   0   0   2  ||   2   5   4   4   1   4   0
  0   0   0   0   0   0   0  ||   4   4   4   3   3   4   4
  3   1   0   3   0  [0]  2  ||   0   4   5   0   4  [6]  2
  1   1   0   0   0   1   3  ||   4   4   5   3   4   4   0

Round 16:
  2  [0]  3   1   0   0   2  ||   0  [5]  0   2   1   4   0
  0   0   0   0   0   0   2  ||   2   5   4   4   1   4   0
  0   0   0   0   0   0   0  ||   4   4   4   3   3   3   3
  3   1   0   3   0   0   1  ||   0   4   5   0   3   3   1
  1   1   0   0   0   0   2  ||   4   4   5   3   3   3   0

Round 17:
  1   0   2   1   0   0   2  ||   0   3   0   1   1   4   0
  0   0   0   0   0   0   2  ||   1   3   3   3   1   4   0
  0   0   0   0   0   0   0  ||   4   4   4   3   3   3   3
  3   1  [0]  3   0   0   1  ||   0   4  [5]  0   3   3   1
  1   1   0   0   0   0   2  ||   4   4   5   3   3   3   0

Round 18:
  1   0   2   1   0   0   2  ||   0   3   0   1   1   4   0
  0   0   0   0   0   0   2  ||   1   3   3   3   1   4   0
  0   0   0   0   0   0   0  ||   3   3   2   2   2   3   3
  3  [0]  0   2   0   0   1  ||   0  [4]  2   0   2   3   1
  1   0   0   0   0   0   2  ||   2   4   2   2   2   3   0

Round 19:
  1   0   2   1   0  [0]  2  ||   0   3   0   1   1  [4]  0
  0   0   0   0   0   0   2  ||   1   3   3   3   1   4   0
  0   0   0   0   0   0   0  ||   2   2   2   2   2   3   3
  2   0   0   2   0   0   1  ||   0   2   2   0   2   3   1
  0   0   0   0   0   0   2  ||   2   2   2   2   2   3   0

Round 20:
  1  [0]  2   1   0   0   1  ||   0  [3]  0   1   1   2   0
  0   0   0   0   0   0   1  ||   1   3   3   3   1   2   0
  0   0   0   0   0   0   0  ||   2   2   2   2   2   2   2
  2   0   0   2   0   0   1  ||   0   2   2   0   2   3   1
  0   0   0   0   0   0   2  ||   2   2   2   2   2   3   0

Round 21:
  0   0   1   1   0   0   1  ||   0   1   0   0   1   2   0
  0   0   0   0   0   0   1  ||   0   1   2   2   1   2   0
  0   0   0   0   0   0   0  ||   2   2   2   2   2   2   2
  2   0   0   2   0  [0]  1  ||   0   2   2   0   2  [3]  1
  0   0   0   0   0   0   2  ||   2   2   2   2   2   3   0

Round 22:
  0   0   1   1   0   0   1  ||   0   1   0   0   1   2   0
  0   0   0   0   0   0   1  ||   0   1   2   2   1   2   0
 [0]  0   0   0   0   0   0  ||  [2]  2   2   2   2   1   1
  2   0   0   2   0   0   0  ||   0   2   2   0   2   1   1
  0   0   0   0   0   0   1  ||   2   2   2   2   2   1   0

Round 23:
  0   0   1   1   0   0   1  ||   0   1   0   0   1   2   0
  0   0  [0]  0   0   0   1  ||   0   1  [2]  2   1   2   0
  0   0   0   0   0   0   0  ||   1   1   2   2   2   1   1
  1   0   0   2   0   0   0  ||   0   1   2   0   2   1   1
  0   0   0   0   0   0   1  ||   1   1   2   2   2   1   0

Round 24:
  0   0   0   0   0   0   1  ||   0   0   0   0   0   2   0
  0   0   0   0   0   0   1  ||   0   0   0   0   0   2   0
  0   0  [0]  0   0   0   0  ||   1   1  [2]  2   2   1   1
  1   0   0   2   0   0   0  ||   0   1   2   0   2   1   1
  0   0   0   0   0   0   1  ||   1   1   2   2   2   1   0

Round 25:
  0   0   0   0   0  [0]  1  ||   0   0   0   0   0  [2]  0
  0   0   0   0   0   0   1  ||   0   0   0   0   0   2   0
  0   0   0   0   0   0   0  ||   1   1   1   1   1   1   1
  1   0   0   1   0   0   0  ||   0   1   1   0   1   1   1
  0   0   0   0   0   0   1  ||   1   1   1   1   1   1   0

Round 26:
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
 [0]  0   0   0   0   0   0  ||  [1]  1   1   1   1   0   0
  1   0   0   1   0   0   0  ||   0   1   1   0   1   1   1
  0   0   0   0   0   0   1  ||   1   1   1   1   1   1   0

Round 27:
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
  0   0  [0]  0   0   0   0  ||   0   0  [1]  1   1   0   0
  0   0   0   1   0   0   0  ||   0   0   1   0   1   1   1
  0   0   0   0   0   0   1  ||   0   0   1   1   1   1   0

Round 28:
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
  0   0   0   0   0   0   0  ||   0   0   0   0   0   0   0
  0   0   0   0   0  [0]  0  ||   0   0   0   0   0  [1]  1
  0   0   0   0   0   0   1  ||   0   0   0   0   0   1   0

Done in 28 rounds

1

これは、深さO(3 ^(n))のツリーを使用して解決できます。ここで、nはすべての正方形の合計です。

最初にO(9 ^ n)のツリーで問題を解決するのは簡単であることを考慮し、考えられる爆撃の場所をすべて考慮してください。例については、Alfeの実装を参照してください。

次に、下から上に爆撃するように作業しても、最小の爆撃パターンを取得できることを認識してください。

  1. 左下から始めます。
  2. 意味のある(上から右への)プレイのみで忘却のために爆弾を撃ちます。
  3. 右に1マス移動します。
  4. ターゲットの値がゼロより大きい場合は、意味のある2つのプレー(真上または真上、右)をそれぞれ考慮し、ターゲットの値を1つ減らし、可能性ごとに新しいブランチを作成します。
  5. もう1つ右に移動します。
  6. ターゲットの値がゼロより大きい場合は、意味のある3つのプレー(左上、右上、右上)をそれぞれ考慮し、ターゲットの値を1つ減らし、可能性ごとに新しい分岐を作成します。
  7. 行が削除されるまで、手順5と6を繰り返します。
  8. 行を上に移動し、パズルが解決するまで手順1〜7を繰り返します。

このアルゴリズムは正しいので

  1. 各行をある時点で完了する必要があります。
  2. 行を完了するには、常にその行の上、下、または行内でプレイする必要があります。
  3. 一番下のクリアされていない行の1つ上または下の行のプレーよりも1つ上のプレーを選択することは、常に同じかまたはより良いです。

実際には、このアルゴリズムは常に理論上の最大値よりも優れています。これは、このアルゴリズムが定期的に近隣を爆破し、検索のサイズを縮小するためです。爆撃ごとに4つの追加ターゲットの値が減少すると仮定すると、アルゴリズムはO(3 ^(n / 4))または約O(1.3 ^ n)で実行されます。

このアルゴリズムはまだ指数関数であるため、検索の深さを制限することをお勧めします。許可される分岐の数を特定の数Xに制限する場合があります。この深さになると、アルゴリズムに、これまでに識別された最良のパス(端子リーフの1つでボード合計の合計が最小になるパス)を選択するように強制します。 )。その後、アルゴリズムはO(3 ^ X)時間で実行されることが保証されていますが、正しい答えが得られるとは限りません。ただし、計算量の増加とより良い解答の間のトレードオフが価値がある場合は、常にXを増やして経験的にテストできます。


1

評価関数、合計:

int f (int ** matrix, int width, int height, int x, int y)
{
    int m[3][3] = { 0 };

    m[1][1] = matrix[x][y];
    if (x > 0) m[0][1] = matrix[x-1][y];
    if (x < width-1) m[2][1] = matrix[x+1][y];

    if (y > 0)
    {
        m[1][0] = matrix[x][y-1];
        if (x > 0) m[0][0] = matrix[x-1][y-1];
        if (x < width-1) m[2][0] = matrix[x+1][y-1];
    }

    if (y < height-1)
    {
        m[1][2] = matrix[x][y+1];
        if (x > 0) m[0][2] = matrix[x-1][y+1];
        if (x < width-1) m[2][2] = matrix[x+1][y+1];
    }

    return m[0][0]+m[0][1]+m[0][2]+m[1][0]+m[1][1]+m[1][2]+m[2][0]+m[2][1]+m[2][2];
}

目的関数:

Point bestState (int ** matrix, int width, int height)
{
    Point p = new Point(0,0);
    int bestScore = 0;
    int b = 0;

    for (int i=0; i<width; i++)
        for (int j=0; j<height; j++)
        {
            b = f(matrix,width,height,i,j);

            if (b > bestScore)
            {
                bestScore = best;
                p = new Point(i,j);
            }
        }

    retunr p;
}

破壊機能:

void destroy (int ** matrix, int width, int height, Point p)
{
    int x = p.x;
    int y = p.y;

    if(matrix[x][y] > 0) matrix[x][y]--;
    if (x > 0) if(matrix[x-1][y] > 0) matrix[x-1][y]--;
    if (x < width-1) if(matrix[x+1][y] > 0) matrix[x+1][y]--;

    if (y > 0)
    {
        if(matrix[x][y-1] > 0) matrix[x][y-1]--;
        if (x > 0) if(matrix[x-1][y-1] > 0) matrix[x-1][y-1]--;
        if (x < width-1) if(matrix[x+1][y-1] > 0) matrix[x+1][y-1]--;
    }

    if (y < height-1)
    {
        if(matrix[x][y] > 0) matrix[x][y+1]--;
        if (x > 0) if(matrix[x-1][y+1] > 0) matrix[x-1][y+1]--;
        if (x < width-1) if(matrix[x+1][y+1] > 0) matrix[x+1][y+1]--;
    }
}

ゴール関数:

bool isGoal (int ** matrix, int width, int height)
{
    for (int i=0; i<width; i++)
        for (int j=0; j<height; j++)
            if (matrix[i][j] > 0)
                return false;
    return true;
}

線形最大化関数:

void solve (int ** matrix, int width, int height)
{
    while (!isGoal(matrix,width,height))
    {
        destroy(matrix,width,height, bestState(matrix,width,height));
    }
}

これは最適ではありませんが、より良い評価関数を見つけることで最適化できます。

..しかし、この問題について考えると、主要な問題の1つが、ある時点でゼロの真ん中に捨てられた数値を取得することであると考えていたため、最小値をゼロに支配する別のアプローチを採用し、次にゼロを可能な限りエスケープします。これにより、既存の最小値などが一般的に最小化されます。


0

この問題のすべては、編集距離を計算することです。動的配列を使用して中間配列間の距離を格納することにより、編集が爆撃で置き換えられる、指定された行列とゼロ行列の間のレーベンシュタイン距離のバリアントを計算するだけです。マトリックスのハッシュをキーとして使用することをお勧めします。擬似Pythonの場合:

memo = {}

def bomb(matrix,i,j):
    # bomb matrix at i,j

def bombsRequired(matrix,i,j):
    # bombs required to zero matrix[i,j]

def distance(m1, i, len1, m2, j, len2):
    key = hash(m1)
    if memo[key] != None: 
        return memo[key]

    if len1 == 0: return len2
    if len2 == 0: return len1

    cost = 0
    if m1 != m2: cost = m1[i,j]
    m = bomb(m1,i,j)
    dist = distance(str1,i+1,len1-1,str2,j+1,len2-1)+cost)
    memo[key] = dist
    return dist

0

これは最初の質問に対する答えでした。彼がパラメータを変更したことに気づかなかった。

すべてのターゲットのリストを作成します。ドロップ(それ自体、およびすべてのネイバー)の影響を受ける正の値の数に基づいて、ターゲットに値を割り当てます。最高値は9です。

影響を受けるターゲットの数(降順)でターゲットを並べ替え、影響を受ける各ターゲットの合計を2番目に降順に並べ替えます。

最高ランクのターゲットに爆弾を投下し、ターゲットを再計算して、すべてのターゲット値がゼロになるまで繰り返します。

これは、常に最適であるとは限りません。例えば、

100011
011100
011100
011100
000000
100011

このアプローチでは、5つの爆弾を取り除く必要があります。最適には、ただし、4でそれを行うことができます。それでも、かなり近づき、バックトラックはありません。ほとんどの状況では、最適であるか、非常に近くなります。

元の問題数を使用して、このアプローチは28の爆弾で解決します。

この方法を示すコードを追加します(ボタン付きのフォームを使用)。

         private void button1_Click(object sender, EventArgs e)
    {
        int[,] matrix = new int[10, 10] {{5, 20, 7, 1, 9, 8, 19, 16, 11, 3}, 
                                         {17, 8, 15, 17, 12, 4, 5, 16, 8, 18},
                                         { 4, 19, 12, 11, 9, 7, 4, 15, 14, 6},
                                         { 17, 20, 4, 9, 19, 8, 17, 2, 10, 8},
                                         { 3, 9, 10, 13, 8, 9, 12, 12, 6, 18}, 
                                         {16, 16, 2, 10, 7, 12, 17, 11, 4, 15},
                                         { 11, 1, 15, 1, 5, 11, 3, 12, 8, 3},
                                         { 7, 11, 16, 19, 17, 11, 20, 2, 5, 19},
                                         { 5, 18, 2, 17, 7, 14, 19, 11, 1, 6},
                                         { 13, 20, 8, 4, 15, 10, 19, 5, 11, 12}};


        int value = 0;
        List<Target> Targets = GetTargets(matrix);
        while (Targets.Count > 0)
        {
            BombTarget(ref matrix, Targets[0]);
            value += 1;
            Targets = GetTargets(matrix);
        }
        Console.WriteLine( value);
        MessageBox.Show("done: " + value);
    }

    private static void BombTarget(ref int[,] matrix, Target t)
    {
        for (int a = t.x - 1; a <= t.x + 1; a++)
        {
            for (int b = t.y - 1; b <= t.y + 1; b++)
            {
                if (a >= 0 && a <= matrix.GetUpperBound(0))
                {
                    if (b >= 0 && b <= matrix.GetUpperBound(1))
                    {
                        if (matrix[a, b] > 0)
                        {
                            matrix[a, b] -= 1;
                        }
                    }
                }
            }
        }
        Console.WriteLine("Dropped bomb on " + t.x + "," + t.y);
    }

    private static List<Target> GetTargets(int[,] matrix)
    {
        List<Target> Targets = new List<Target>();
        int width = matrix.GetUpperBound(0);
        int height = matrix.GetUpperBound(1);
        for (int x = 0; x <= width; x++)
        {
            for (int y = 0; y <= height; y++)
            {
                Target t = new Target();
                t.x = x;
                t.y = y;
                SetTargetValue(matrix, ref t);
                if (t.value > 0) Targets.Add(t);
            }
        }
        Targets = Targets.OrderByDescending(x => x.value).ThenByDescending( x => x.sum).ToList();
        return Targets;
    }

    private static void SetTargetValue(int[,] matrix, ref Target t)
    {
        for (int a = t.x - 1; a <= t.x + 1; a++)
        {
            for (int b = t.y - 1; b <= t.y + 1; b++)
            {
                if (a >= 0 && a <= matrix.GetUpperBound(0))
                {
                    if (b >= 0 && b <= matrix.GetUpperBound(1))
                    {
                        if (matrix[ a, b] > 0)
                        {
                            t.value += 1;
                            t.sum += matrix[a,b];
                        }

                    }
                }
            }
        }

    }

必要なクラス:

        class Target
    {
        public int value;
        public int sum;
        public int x;
        public int y;
    }

1
最適ではありません。反例:09090このアプローチには18の爆弾が必要です。それは9で行うことができます
Mysticial

@Mysticial答えを完全に読みませんでした。影響を受ける非ゼロフィールドの数に基づいているため、このアルゴリズムは真ん中のゼロを爆撃し、9ドロップで実行されます。9という高い値は、最大8つのネイバーとそれ自体が存在するためです。
アンソニークイーン

その後方法については10101010010100
Mysticial 2013年

@Mysticial最初は、最初のゼロ、次に最後のゼロがヒットします。それは2滴になります。これは、爆弾を投下するたびに、次善のターゲットを再計算するためです。最後の例では、再び中央のゼロです。一滴。
アンソニークイーン

1
@AnthonyQueen:これは機能しません。私の反例については、chat.stackoverflow.com / transcript / message / 82242738224273を参照してください。
nneonneo
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.