web-dev-qa-db-ja.com

爆弾投下アルゴリズム

を持っています 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:上の行をクリアするために最小限の爆弾を必要とする同じ状況がいくつかある場合、行の「左側」にあるほとんどの爆弾を使用するものを選択すると思います。それでも正しい証拠はありますか?

212
abc

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

説明には2つの部分、アルゴリズム、およびアルゴリズムが最適なソリューションを提供する理由があります。前者は後者なしでは意味をなさないので、理由から始めましょう。

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

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

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

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

しかし、私たちは貪欲になることができることを知っています。レイヤー2の爆弾は、レイヤー3の戦略的に配置された爆弾よりもレイヤー2を0に減らすのに効率的ではないためです。レイヤー2に置かれた爆弾ができるレイヤー2の。したがって、貪欲になることは決してこの害になりません(この意味での欲張り)。

そのため、次の内層を爆撃して許可者を0に減らす最適な方法を見つけるだけです。

最初にコーナーを0に爆撃してもけがをすることはありません。内側のレイヤーのコーナーのみが到達できるため、選択の余地はありません(そして、コーナーに到達できる境界上の爆弾は、内層の角からの爆発半径)。

そうすると、0コーナーに隣接する境界上の正方形には、内側の層から2つの正方形しか到達できません。

0       A       B

C       X       Y

D       Z

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

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

したがって、レイヤー内の1つの正方形を最適に0に減らすことができる場合、アルゴリズムがあります(ループを切断し、終点を持つ直線ができたため)。私は、最低値の2つの正方形内の最高値が可能な限り最小になるように最低値の正方形に隣接する爆撃(2つの選択肢を与える)(これを管理するために爆撃を分割する必要があるかもしれません)が最適だと思いますが、 (まだ?)証拠を持っていません。

38
psr

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と呼びます。このアルゴリズムはいつ失敗しますか?


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

25
Colonel Panic

時間がなくなったため、部分的な解決策だけで停止する必要がありましたが、この部分的な解決策でさえ、この問題を解決するための1つの潜在的なアプローチに関する洞察を提供することを願っています

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

0 4 2 1 3 0 1

どういうわけか、4スポットまたはその周辺を4回爆撃して0に下げる必要があることを知っています。スポットの左が小さいため、0または4を爆撃してもメリットはありません。 2。実際、2スポットが0になるまで4を爆撃することは、少なくとも4を0にする他の戦略と同じくらい良いと信じています(ただし、厳密な証拠はありません)。次のような戦略で:

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つのスペースを無料で取得することをお勧めします。これを考えると、エッジの内側にあるリングを爆撃することは、少なくともエッジを爆撃するのと同じくらい良いと言えます。さらに、これを、Edgeの内側で正しいものを爆撃することが実際にEdgeスペースを0に下げる唯一の方法であるという直感と組み合わせることができます。さらに、最適な戦略を見つけることは簡単です(少なくとも他の戦略と同じくらい良い)コーナー番号を0にする。これをすべてまとめると、2次元空間での解にはるかに近づくことができます。

コーナーピースに関する観察から、開始ボードからすべてのコーナーにゼロがあるボードに移動するための最適な戦略を知っていると確信できます。これは、そのようなボードの例です(上記の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

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

could上から線形戦略を適用する(x行の対応するスペースを爆撃する)、自分自身についてonly一番上の行にのみ。次のようになります。

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回の爆撃で非常に明白になります。 2行目の最初の列の4の数値を減らす唯一の爆弾サイトが最初のxyであることを考えると、明らかです。最後の2回の爆撃は、最初のxを爆撃するよりも明らかに劣ります。これはまったく同じことを行います(一番上の行の最初のスポットに関して、他のクリア方法はありません)。現在の戦略は最適ではないことが実証されているため、戦略の修正が明らかに必要です。

この時点で、複雑さを一段階下げて、1つのコーナーだけに集中できます。これについて考えてみましょう。

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

4でスペースをゼロにする唯一の方法は、xy、およびzの組み合わせを爆撃することだけであることは明らかです。いくつかのアクロバットを思い浮かべて、最適な解決策はxを3回、abの3回爆撃することだと確信しています。今、私はその解決策にどのように到達したかを考え出す問題であり、それがこのローカルな問題を解決するために使用できる直感を明らかにするなら。 yzスペースの爆撃がないことに気付きました。これらのスペースを爆撃することが理にかなっているコーナーを見つけようとすると、次のようなコーナーが得られます。

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

これについては、yを5回、zを5回爆撃することが最適な解決策であることは明らかです。さらに一歩進めましょう。

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

ここで、最適な解決策は、abを6回、次にxを4回爆撃することであることが同様に直感的に感じられます。

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

うまくいけば続けられます!

12
Steven

更新された質問に対して、シンプルな欲張りアルゴリズムが最適な結果を提供します。

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などをきれいにします。

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


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

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

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

enter image description here

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

10
Evgeny Kluev

この問題は integer programming problemと表現できます。 (これは、この問題に対処するための可能な解決策の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 hard。

9
Luka Rahne

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

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. Pをminimumsリストに追加します。
  4. すべてのセルが選択不可能になるまで、手順1を繰り返します。
  5. minimumsリストを合計して、下限を取得します。
9
Lie Ryan

これは貪欲なアプローチです:

  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」の結果を追跡する必要があります。

9
SidR

newの問題は、行全体で値が減少しないため、非常に簡単に解決できます。

左の列に最大の数値が含まれていることに注意してください。したがって、最適なソリューションでは、まずこの列をゼロに減らす必要があります。したがって、この列に対して1-D爆撃を実行し、その中のすべての要素をゼロに減らすことができます。爆弾を2列目に落として、最大のダメージを与えます。ここには1Dのケースを扱った多くの投稿があるので、そのケースをスキップしても安全だと思います。 (説明してほしい場合は、できます。)プロパティが減少するため、左端の3つの列はすべてゼロになります。ただし、左の列をゼロにする必要があるため、ここでは最低数の爆弾を使用することを証明します。

ここで、左の列がゼロになったら、ゼロになった左端の3つの列を切り取り、縮小されたマトリックスで繰り返します。これは、各段階で証明可能な最小数の爆弾を使用するため、最適なソリューションを提供する必要があります。

8
nneonneo

分枝限定法を使用した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]

出力

enter image description here

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

次の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秒で解決できました)。

enter image description here

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

3
darksky

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

コメントで指摘したように、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番目の行がある場合)。だから2番目に撃つ。最初の行の1Dタスクを解きます。 (nullにする必要があるため)。降りる。最後の行を忘れないでください。

3
RiaD

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

#!/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 __== '__main__':
  import sys
  sys.exit(main(sys.argv))
3
Alfe

ここでは、線形プログラミングのアプローチが非常に役立つようです。

LetPm x n位置の値を持つ行列:

Matrix of positions

bomb行列B(x、y)を定義しましょうm x n1≤x≤m1≤y≤n以下のとおり

Bomb matrix

そのような方法で

Values of positions in bomb matrix

例えば:

B(3, 3)

だから私たちは行列を探していますBm x n =[bij]

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

    B as a sum of bomb matrices

    qij爆弾の量になります位置に落ちますpij

  2. pij -bij ≤0(より簡潔にするため、P-B≤0

また、[〜#〜] b [〜#〜]は合計を最小化する必要があります sum of quantities of bombs

また、[〜#〜] b [〜#〜]を前のい行列として書くこともできます。

B as a matrix of sum of quantities

そして、P-B≤0(つまり、P≤B)以下の非常に線形な不等式システムがあります。

Relationship between number of bombs dropped and values in positions

存在qmn x 1と定義

Vector of quantities

pmn x 1と定義

Values of P distributed as a vector

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

さて、次のような最小限の問題があります。

The system we have to solve

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

LaTeX式から写真を作成するこの素晴らしいサイト )に特別な感謝を捧げます。

3
brandizzi

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

代わりに、単純な貪欲なヒューリスティックを使用します。これはコーナーを爆破するで、最大のものから開始します。

この例では、{2、1、6、4}の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、1}の2つの新しいコーナーができました。私たちはそれらを爆撃し、さらに3回爆撃します。マトリックスを4 x 4に縮小しました。 1つのコーナー、左上があります。私たちはそれを爆撃します。これで、{5、3}の2つのコーナーが残っています。 5が最大のコーナーであるため、最初に5回爆撃し、最後に他のコーナーで3回爆撃します。合計は13 + 3 + 3 + 1 + 5 + 3 = 28です。

3
Tyler Durden

爆弾の量を最小限に抑えるには、単に損傷の量を最大化する必要があると思います。そのためには、最も強い力を持つ領域を確認する必要があります。強いです。そしてそこに爆弾があります。そしてフィールドが平らになるまで行います。

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');
2
CaldasGSM

以下は、コーナーの良好な特性を一般化するソリューションです。

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

_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)_のdrop pointは、_(x, y)_の値を減らします。また、他のセルの値が減少する場合があります。
  • _(x, y)_のドロップポイントaは、betterよりもドロップポイントb_(x, y)_の場合、セルの適切なスーパーセットの値を減少させる場合b 減少します。
  • 他に適切なドロップポイントがない場合、ドロップポイントはmaximalです。
  • _(x, y)_の2つのドロップポイントは、同じセルセットを減らす場合、equivalentです。
  • _(x, y)_のドロップポイントは、_(x, y)_のすべての最大ドロップポイントと等しい場合、perfectです。

_(x, y)_の完全なドロップポイントがある場合、_(x, y)_の完全なドロップポイントの1つに爆弾を落とすよりも_(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
_
2
Tammo Freese

爆弾の数を最小限に抑えるには、すべての爆弾の効果を最大化する必要があります。これを実現するには、すべてのステップで最適なターゲットを選択する必要があります。それを合計する各ポイントと8つの隣人です-このポイントを爆撃する効率的な量として使用できます。これにより、ほぼ最適な爆弾シーケンスが提供されます。

[〜#〜] upd [〜#〜]:爆弾は非効率的であるため、ゼロの数も考慮する必要があります。実際、問題はヒットしたゼロの数を最小限にすることです。しかし、どのステップがこの目的に近づくのかはわかりません。問題はNP完全であるという考えに同意します。私は貪欲なアプローチを推し進めます。これは、実際に近い答えを与えます。

2
Mikhail

別のアイデアを次に示します。

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

次に、スペースのリストをウェイトでソートし、ウェイトが最も高いスペースを爆撃します。言うまでもなく、これは私たちの支出に見合った価値があります。

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

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

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

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

編集:以下のMysticialの反例は、重みの関係にかかわらず、実際にこれが最適であるとは限らないことを示しています。特定のステップで可能な限り重量を減らすと、実際には残りの爆弾が広がりすぎて、最初のステップでわずかに貪欲な選択ができるほど、2番目のステップの後の累積的な削減が達成されません。結果は爆撃の順序に左右されないという概念にやや誤解していました。彼らは順序に影響されず、一連の爆撃を受けて、最初から別の順序で再生し、同じ結果になりますボード。しかし、各爆撃を個別に検討できるということにはなりません。または、少なくとも、各爆撃は、それがその後の爆撃のためにボードをどれだけうまくセットアップするかを考慮した方法で考慮されなければなりません。

2
Tim Goodman

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

それが最良の方法であると考えに来てください。そこに爆弾を投下して削除したポイントを保存する別のマトリックスを作成し、最大ポイントを持つセルを選択し、そこに爆弾を投下してポイントマトリックスを更新し、続行します。例:

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

1
cosmin.danisor
  1. 国境を爆撃しない(正方形に国境のない隣人がいない限り)
  2. ゼロコーナー。
  3. コーナーをゼロにするために、コーナーの値を1正方形離れた対角線にドロップします(唯一の非境界隣接)
  4. これにより、新しいコーナーが作成されます。 2に行く

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

OPの例では、5以外に2(1 + 1または2)をドロップしても、5にドロップしたマスにヒットすることはありません。したがって、5に2をドロップするだけです(そして、左下1に6をドロップします...)

この後、元々1(現在は0)であったコーナー(左上)をクリアする方法は1つしかありません。それは、B3(Excelのような表記)に0をドロップすることです。等々。

A列とE列全体と1行と7行をクリアした後にのみ、1つのレイヤーのクリアを開始します。

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

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


よく眠った後、これは真実ではないことに気づきました。検討する

  ABCDE    
1 01000
2 10000
3 00000
4 00000

私のアプローチでは、B2に落とすだけで十分なときにB3とC2に爆弾を落とす

1
Alpedar

28の動きもありました。次の最高の動きのために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

評価関数、合計:

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つはある時点でゼロの真ん中に放棄された数字を取得することだと考えていたので、最小値をゼロに支配する別のアプローチを採用してから、可能な限りゼロをエスケープします。これにより、既存の最小値が一般的に最小化されます。

1
Khaled.K

ボードの位置に1、2、...、n x mの番号を付けたとします。一連の爆弾投下は、このセット内の一連の数字で表すことができ、数字を繰り返すことができます。ただし、ボードへの影響は、爆弾を投じる順序に関係なく同じです。そのため、実際に爆弾投下の選択肢はnxm番号のリストとして表すことができます。 、2番目の数字は位置2に投下された爆弾の数などを表します。このnxm番号のリストを「キー」と呼びましょう。

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

ただし、n、m、およびグリッド内の数値のサイズによっては、このアプローチのメモリ要件が過剰になる場合があります。 N + 1のすべての結果を計算したら、N個の爆弾投下のすべての結果を破棄できます。そしてもちろん、それを要するという犠牲を払って何かをキャッシュすることはできませんでしたalotより長い-動的プログラミングのアプローチは、メモリをスピードと引き換えにします。

1
Tim Goodman

強引な !

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

次のように、いくつかの再帰を使用します。

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
sharp12345

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

0010000
1000100
0000001
1000000
0000001
1000100
0010000

この場合の最適なソリューションのサイズは5です。これは、エッジが9サイクルの頂点の最小カバーのサイズだからです。

このケースは、特に、少数の人々が投稿した線形計画法の緩和が正確ではなく、機能しないこと、およびその他のすべての悪いことを示しています。私は「平面立方体グラフの頂点をできるだけ少ないエッジでカバーする」ことをあなたの問題に減らすことができると確信しています。それは欲張り/山登りの解決策のいずれかが機能するかどうか疑問に思います。

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

[〜#〜] edit [〜#〜]:コンテスト( http://deadline24.pl )言語に依存しない;入力ファイルの束を送信し、出力を送信します。したがって、最悪の多項式時間で実行されるものは必要ありません。特に、入力を見る

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

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

1
tmyklebu

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

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

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

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

定義してみましょう抵抗のポイントボード内のポイントになるように最も爆撃不可能なポイント + の最大数

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

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

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

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

抵抗の最初のポイントは明らかに私たちのコーナーです。 「境界外」のポイントは爆撃可能ではありません(各コーナーの範囲外に5つのポイントがあります)。そこで、最初に対角線上にあるポイントを爆撃します。

アルゴリズム:

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

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

後で実際のコードを書きます

1
Etai

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

したがって、私の方法は、各セルの爆撃効率メトリックを計算し、最高値でセルを爆撃します....すべてを平坦化するまでプロセスを繰り返します。単純な潜在的ダメージ(つまり、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
zeFrenchy

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

  • g:これまでに投下された爆弾の数
  • h:グリッドのすべての値を9で割った合計(これが最良の結果です。つまり、許容可能なヒューリスティックがあります)
1
キキジキ

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

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

次に、ボトムアップで爆撃を行いながら、最小限の爆撃パターンを取得できることを認識してください。

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

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

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

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

このアルゴリズムはまだ指数関数的であるため、検索の深さを制限するのが賢明でしょう。許可されるブランチの数をある数Xに制限し、この深さになると、アルゴリズムがこれまでに特定した最適なパス(ターミナルリーフの1つでボードの合計の合計が最小になるパス)を選択するように強制します)。この場合、アルゴリズムはO(3 ^ X)時間で実行されることが保証されますが、正しい答えを取得することは保証されません。ただし、計算の増加とより良い回答とのトレードオフに価値がある場合は、Xを常に増やして経験的にテストできます。

1
Ben Haley

これまでにいくつかの答えは指数関数的な時間を与えており、いくつかは動的プログラミングに関係しています。それらが必要かどうかは疑問です。

私の解決策はO(mnS)です。ここでm、nはボードの寸法、[〜#〜] s [〜#〜]はすべての整数の合計です。アイデアはかなりブルートフォースです:毎回最も多く殺すことができ、0で終わる場所を見つけます。

与えられたボードに28の動きを与え、各ドロップ後にボードを印刷します。

完全な自明のコード:

import Java.util.Arrays;

public class BombMinDrops {

    private static final int[][] 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}};
    private static final int ROWS = BOARD.length;
    private static final int COLS = BOARD[0].length;
    private static int remaining = 0;
    private static int dropCount = 0;
    static {
        for (int i = 0; i < ROWS; i++) {
            for (int j = 0; j < COLS; j++) {
                remaining = remaining + BOARD[i][j];
            }
        }
    }

    private static class Point {
        int x, y;
        int kills;

        Point(int x, int y, int kills) {
            this.x = x;
            this.y = y;
            this.kills = kills;
        }

        @Override
        public String toString() {
            return dropCount + "th drop at [" + x + ", " + y + "] , killed " + kills;
        }
    }

    private static int countPossibleKills(int x, int y) {
        int count = 0;
        for (int row = x - 1; row <= x + 1; row++) {
            for (int col = y - 1; col <= y + 1; col++) {
                try {
                    if (BOARD[row][col] > 0) count++;
                } catch (ArrayIndexOutOfBoundsException ex) {/*ignore*/}
            }
        }

        return count;
    }

    private static void drop(Point here) {
        for (int row = here.x - 1; row <= here.x + 1; row++) {
            for (int col = here.y - 1; col <= here.y + 1; col++) {
                try {
                    if (BOARD[row][col] > 0) BOARD[row][col]--;
                } catch (ArrayIndexOutOfBoundsException ex) {/*ignore*/}
            }
        }

        dropCount++;
        remaining = remaining - here.kills;
        print(here);
    }

    public static void solve() {
        while (remaining > 0) {
            Point dropWithMaxKills = new Point(-1, -1, -1);
            for (int i = 0; i < ROWS; i++) {
                for (int j = 0; j < COLS; j++) {
                    int possibleKills = countPossibleKills(i, j);
                    if (possibleKills > dropWithMaxKills.kills) {
                        dropWithMaxKills = new Point(i, j, possibleKills);
                    }
                }
            }

            drop(dropWithMaxKills);
        }

        System.out.println("Total dropped: " + dropCount);
    }

    private static void print(Point drop) {
        System.out.println(drop.toString());
        for (int[] row : BOARD) {
            System.out.println(Arrays.toString(row));
        }

        System.out.println();
    }

    public static void main(String[] args) {
        solve();
    }

}
0
PoweredByRice

最も遅いが最も単純でエラーのないアルゴリズムは、すべての有効な可能性を生成してテストすることです。この場合、結果は非常に簡単です(結果は爆弾の配置順序に依存しないため)。

  1. n回bompを適用する関数を作成します
  2. すべてのbompb-placement/bomb-countの可能性のループを作成します(matrix == 0のときに停止します)
  3. 常に最良のソリューションを覚えておいてください。
  4. ループの終わりには、最良の解決策があります
    • 爆弾の数だけでなく、その配置も

コードは次のようになります。

void copy(int **A,int **B,int m,int n)
    {
    for (int i=0;i<m;i++)
     for (int j=0;i<n;j++)
       A[i][j]=B[i][j];
    }

bool is_zero(int **M,int m,int n)
    {
    for (int i=0;i<m;i++)
     for (int j=0;i<n;j++)
      if (M[i][j]) return 0;
    return 1;
    }

void drop_bomb(int **M,int m,int n,int i,int j,int N)
    {
    int ii,jj;
    ii=i-1; jj=j-1; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj])) { M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; }
    ii=i-1; jj=j  ; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj])) { M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; }
    ii=i-1; jj=j+1; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj])) { M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; }
    ii=i  ; jj=j-1; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj])) { M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; }
    ii=i  ; jj=j  ; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj])) { M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; }
    ii=i  ; jj=j+1; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj])) { M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; }
    ii=i+1; jj=j-1; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj])) { M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; }
    ii=i+1; jj=j  ; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj])) { M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; }
    ii=i+1; jj=j+1; if ((ii>=0)&&(ii<m)&&(jj>=0)&&(jj<n)&&(M[ii][jj])) { M[ii][jj]-=N; if (M[ii][jj]<0) M[ii][jj]=0; }
    }

void solve_problem(int **M,int m,int n)
    {
    int i,j,k,max=0;
    // you probably will need to allocate matrices P,TP,TM yourself instead of this:
    int P[m][n],min;             // solution: placement,min bomb count
    int TM[m][n],TP[m][n],cnt;   // temp
    for (i=0;i<m;i++)            // max count of bomb necessary to test
     for (j=0;j<n;j++)
      if (max<M[i][j]) max=M[i][j];
    for (i=0;i<m;i++)            // reset solution
     for (j=0;j<n;j++)
      P[i][j]=max;
    min=m*n*max; 
        copy(TP,P,m,n); cnt=min;

    for (;;)  // generate all possibilities
        {
        copy(TM,M,m,n);
        for (i=0;i<m;i++)   // test solution
         for (j=0;j<n;j++)
          drop_bomb(TM,m,n,TP[i][j]);
        if (is_zero(TM,m,n))// is solution
         if (min>cnt)       // is better solution -> store it
            {
            copy(P,TP,m,n); 
            min=cnt;    
            }
        // go to next possibility
        for (i=0,j=0;;)
            {
            TP[i][j]--;
            if (TP[i][j]>=0) break;
            TP[i][j]=max;
                 i++; if (i<m) break;
            i=0; j++; if (j<n) break;
            break;
            }
        if (is_zero(TP,m,n)) break;
        }
    //result is in P,min
    }

これは多くの方法で最適化できます。..最も簡単なのは、Mマトリックスでソリューションをリセットすることですが、最大値とTP [] []デクリメントコードを変更する必要があります

0
Spektre

これは最初に尋ねられた質問に対する答えでした。彼がパラメータを変更したことに気がつきませんでした。

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

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

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

同意しますが、これはalways最適ではありません。例えば、

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;
    }
0
Anthony Queen

この問題はすべて、編集距離を計算することです。動的プログラミングを使用して中間配列間の距離を保存し、指定された行列とゼロ行列の間のレーベンシュタイン距離のバリアントを単純に計算します。行列のハッシュをキーとして使用することをお勧めします。擬似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
Aerimore