Algorithmus zum Abwerfen von Bomben


212

Ich habe eine n x mMatrix, die aus nicht negativen ganzen Zahlen besteht. Beispielsweise:

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

"Eine Bombe fallen lassen" verringert die Anzahl der Zielzellen und aller acht Nachbarn um eins auf ein Minimum von Null.

x x x 
x X x
x x x

Was ist ein Algorithmus, der die Mindestanzahl von Bomben bestimmt, die erforderlich sind, um alle Zellen auf Null zu reduzieren?

B Option (Da ich kein aufmerksamer Leser bin)

Eigentlich ist die erste Version des Problems nicht die, auf die ich eine Antwort suche. Ich habe die ganze Aufgabe nicht sorgfältig gelesen, es gibt zusätzliche Einschränkungen, sagen wir:

Was ist mit einem einfachen Problem, wenn die Reihenfolge in der Reihe nicht erhöht werden darf:

8 7 6 6 5 ist mögliche Eingabesequenz

7 8 5 5 2 ist nicht möglich, da 7 -> 8 in einer Sequenz wachsen.

Vielleicht hilft es, eine Antwort für einen "einfacheren" Fall zu finden, um eine Lösung für einen schwierigeren Fall zu finden.

PS: Ich glaube, wenn wir in mehreren Situationen minimale Bomben benötigen, um die obere Linie zu löschen, wählen wir eine, die die meisten Bomben auf der "linken Seite" der Reihe verwendet. Noch ein Beweis, der richtig sein könnte?


4
Nun, ich finde nur, dass einige Felder wie in Beispiel 2 3 1 5 übersprungen werden können. Das Ablegen auf 2,3,1 ist sinnlos, weil das Ablegen auf sie einen Teilmengenschaden verursacht, den wir durch Ablegen auf 5 verursachen können. Aber nicht Finden Sie heraus, wie es global funktioniert (wenn es richtig ist). Für das Löschen von 2 müssen 2 Bomben verwendet werden, die auf einen der Nachbarn abgeworfen wurden, und 5 enthält andere Schadenssätze. Aber dann weiß ich nicht, was ich später tun soll, denn wenn Sie es umschreiben (nachdem Sie es verringert haben), haben Sie zwei Möglichkeiten (es gibt keine einzige Menge an Schaden).
abc

23
Ist das zufällig NP-schwer? Es scheint eine Variante des Maximum-Coverage-Problems zu sein .
Mysticial

14
+1 für das Geben von etwas Interessantem zum Nachdenken
Nick Mitchinson

3
@Kostek, tolles Problem! Bitte posten Sie den Link.
Colonel Panic

5
Vielleicht sollten Sie klarstellen, dass die Frage lautet: what's the minimum amount of bombs required to clean the board?Bedeutet dies, dass es nicht unbedingt erforderlich ist, ein tatsächliches Bombenmuster zu finden, sondern nur die minimale Anzahl von Bomben?
Lie Ryan

Antworten:


38

Es gibt eine Möglichkeit, dies auf ein einfaches Unterproblem zu reduzieren.

Die Erklärung besteht aus zwei Teilen: dem Algorithmus und dem Grund, warum der Algorithmus eine optimale Lösung bietet. Das erste macht ohne das zweite keinen Sinn, also beginne ich mit dem Warum.

Wenn Sie daran denken, das Rechteck zu bombardieren (nehmen Sie ein großes Rechteck an - noch keine Randfälle), können Sie sehen, dass die einzige Möglichkeit, das hohle Rechteck der Quadrate am Umfang auf 0 zu reduzieren, darin besteht, entweder den Umfang oder das hohle Rechteck von zu bombardieren Quadrate direkt innerhalb des Umfangs. Ich werde die Umfangsebene 1 und das Rechteck darin Ebene 2 nennen.

Eine wichtige Erkenntnis ist, dass es keine Punktbombenschicht 1 gibt, da der "Explosionsradius", den Sie dadurch erhalten, immer im Explosionsradius eines anderen Quadrats aus Schicht 2 enthalten ist. Sie sollten sich leicht davon überzeugen können.

Wir können das Problem also darauf reduzieren, einen optimalen Weg zu finden, um den Umfang zu bombardieren, und dies dann wiederholen, bis alle Quadrate 0 sind.

Aber das wird natürlich nicht immer eine optimale Lösung finden, wenn es möglich ist, den Umfang nicht optimal zu bombardieren, aber durch die Verwendung von X zusätzlichen Bomben wird das Problem der Reduzierung der inneren Schicht durch> X Bomben einfacher. Wenn wir also die Permiter-Schicht eins nennen und zusätzliche X-Bomben irgendwo in Schicht 2 (direkt innerhalb von Schicht 1) platzieren, können wir den Aufwand für das spätere Bombardieren von Schicht 2 um mehr als X reduzieren? Mit anderen Worten, wir müssen beweisen, dass wir bei der Reduzierung des Außenumfangs gierig sein können.

Aber wir wissen, dass wir gierig sein können. Weil keine Bombe in Schicht 2 jemals effizienter sein kann, um Schicht 2 auf 0 zu reduzieren, als eine strategisch platzierte Bombe in Schicht 3. Und aus dem gleichen Grund wie zuvor - es gibt immer eine Bombe, die wir in Schicht 3 platzieren können und die jedes Quadrat betrifft von Schicht 2, die eine in Schicht 2 platzierte Bombe kann. Es kann uns also niemals schaden, gierig zu sein (in diesem Sinne gierig).

Wir müssen also nur den optimalen Weg finden, um den Permiter durch Bombardierung der nächsten inneren Schicht auf 0 zu reduzieren.

Wir werden nie verletzt, wenn wir zuerst die Ecke auf 0 bombardieren, weil nur die Ecke der inneren Schicht sie erreichen kann, so dass wir wirklich keine Wahl haben (und jede Bombe am Umfang, die die Ecke erreichen kann, hat einen Explosionsradius in der Strahlradius von der Ecke der inneren Schicht).

Sobald wir dies getan haben, können die Quadrate am Umfang neben der 0-Ecke nur durch 2 Quadrate von der inneren Schicht erreicht werden:

0       A       B

C       X       Y

D       Z

Zu diesem Zeitpunkt ist der Umfang effektiv eine geschlossene eindimensionale Schleife, da jede Bombe 3 benachbarte Quadrate reduziert. Abgesehen von einigen Verrücktheiten in der Nähe der Ecken kann X A, B, C und D "treffen".

Jetzt können wir keine Explosionsradius-Tricks verwenden - die Situation jedes Quadrats ist symmetrisch, mit Ausnahme der seltsamen Ecken, und selbst dort ist kein Explosionsradius eine Teilmenge eines anderen. Beachten Sie, dass die Lösung trivial ist, wenn dies eine Linie (wie Colonel Panic erläutert) anstelle einer geschlossenen Schleife wäre. Die Endpunkte müssen auf 0 reduziert werden, und es schadet Ihnen nie, die Punkte neben den Endpunkten zu bombardieren, da der Explosionsradius wiederum eine Obermenge ist. Sobald Sie Ihren Endpunkt auf 0 gesetzt haben, haben Sie immer noch einen neuen Endpunkt. Wiederholen Sie diesen Vorgang (bis alle Zeilen 0 sind).

Wenn wir also ein einzelnes Quadrat in der Ebene optimal auf 0 reduzieren können, haben wir einen Algorithmus (weil wir die Schleife geschnitten haben und jetzt eine gerade Linie mit Endpunkten haben). Ich glaube, dass Bombenangriffe neben dem Quadrat mit dem niedrigsten Wert (mit 2 Optionen), so dass der höchste Wert innerhalb von 2 Quadraten dieses niedrigsten Werts das minimal mögliche ist (Sie müssen möglicherweise Ihre Bombenangriffe aufteilen, um dies zu erreichen), aber ich bin optimal habe (noch?) keinen Beweis.


+1 - Ich wollte etwas Ähnliches schreiben. Ich denke du hast es!
Rex Kerr

5
@beaker, bitte lesen Sie das Problem sorgfältig durch. Durch das Bombardieren eines Quadrats werden alle acht Nachbarn reduziert , sodass seine Annahme dort tatsächlich richtig ist.
Darksky

20
But, we do know we can be greedy...- Ich kaufe das nicht. Betrachten Sie 1 1 2 1 1 2den Umfang. Die Mindestanzahl an Bomben beträgt 4, es gibt jedoch drei verschiedene Lösungen. Jede Lösung hat unterschiedliche Auswirkungen auf die nächste Schicht. Solange es mehrere Minimallösungen für einen Perimeter gibt, können Sie den Perimeter nicht vollständig isolieren, ohne die inneren Schichten zu berücksichtigen. Ich glaube wirklich nicht, dass dieses Problem ohne Rückverfolgung gelöst werden kann.
user1354557

4
Ich habe über diese Lösung nachgedacht, aber sie sieht nicht so einfach aus. Es ist wahr, dass Sie eine Bombe auf Schicht 2 abwerfen können, um Schicht 1 zu reinigen. Wenn es jedoch mehrere Lösungen gibt, wirken sie sich auf Lösungen für höhere Schichten aus.
Luka Rahne

12
@psr: Das funktioniert nicht. Die für die äußere Schicht optimale Bombenmethode ist möglicherweise nicht global optimal. Beispiel : 0011100 0100010 0000000 0000000 1110111. Der optimale Weg, um die erste Schicht zu bombardieren, besteht darin, in der Mitte der zweiten Reihe zu bombardieren und insgesamt drei Bomben zu nehmen, um die äußere Schicht zu töten. Aber dann braucht man zwei Bomben, um sich um die nächste Schicht zu kümmern. Optimal sind insgesamt nur vier Bomben erforderlich: zwei für die ersten beiden Reihen und zwei für die letzte Reihe.
Nneonneo

26

Pólya sagt: "Wenn Sie ein Problem nicht lösen können, gibt es ein einfacheres Problem, das Sie lösen können: Finden Sie es."

Das offensichtlich einfachere Problem ist das eindimensionale Problem (wenn das Gitter eine einzelne Zeile ist). Beginnen wir mit dem einfachsten Algorithmus - dem gierigen Bombardieren des größten Ziels. Wann geht das schief?

Angesichts dessen 1 1 1ist der gierige Algorithmus gleichgültig, welche Zelle er zuerst bombardiert. Natürlich ist die mittlere Zelle besser - sie setzt alle drei Zellen gleichzeitig auf Null. Dies legt einen neuen Algorithmus A nahe, "Bombe, um die verbleibende Summe zu minimieren". Wann geht dieser Algorithmus schief?

Angesichts dessen 1 1 2 1 1ist Algorithmus A gleichgültig zwischen dem Bombardieren der 2., 3. oder 4. Zelle. Aber die 2. Zelle zu bombardieren, um zu verlassen, 0 0 1 1 1ist besser als die 3. Zelle zu bombardieren, um zu verlassen 1 0 1 0 1. Wie kann man das beheben? Das Problem beim Bombardieren der 3. Zelle besteht darin, dass wir links und rechts arbeiten müssen, was separat erfolgen muss.

Wie wäre es mit "Bombe, um die verbleibende Summe zu minimieren, aber das Minimum links (von wo wir bombardiert haben) plus das Minimum rechts zu maximieren". Nennen Sie diesen Algorithmus B. Wann geht dieser Algorithmus schief?


Bearbeiten: Nach dem Lesen der Kommentare stimme ich zu, dass ein viel interessanteres Problem darin besteht, das eindimensionale Problem so zu ändern, dass sich die Enden verbinden. Würde gerne Fortschritte sehen.


40
Ich bin mir nicht sicher, warum diese Antwort so viele positive Stimmen erhält - der 1D-Fall ist fast trivial, bombardieren Sie einfach immer das Element rechts vom ersten positiven Element. Dies funktioniert, weil es immer genau einen optimalen Weg gibt, ein Element zu bombardieren, das links nur Nullen enthält. Dies kann auf 2D erweitert werden, um die Eckquadrate optimal zu entfernen, aber ich sehe keinen offensichtlichen Weg, um es darüber hinaus zu erweitern ...?
BlueRaja - Danny Pflughoeft

3
@BlueRaja, ich habe positiv gestimmt, weil es deutlich gemacht hat, dass der in den anderen Antworten diskutierte gierige Ansatz unzureichend war (zumindest musste er durch zusätzliche Kriterien ergänzt werden). Einige Zielentscheidungen, selbst wenn sie zu einer gleichmäßigen Verringerung der Gesamtzahl führen, können dazu führen, dass die Dinge weiter verteilt sind als andere. Ich denke, dies ist eine nützliche Erkenntnis für das 2D-Problem.
Tim Goodman

3
Und im Allgemeinen ist "Wenn Sie im 2D-Fall stecken bleiben, versuchen Sie zuerst den 1D-Fall" ein guter Rat.
Tim Goodman

21
@ Tim: "'versuche zuerst den 1D-Fall' ist ein guter Rat" Ja, das ist es, was es zu einem ausgezeichneten Kommentar machen würde; aber es ist keine Antwort ...
BlueRaja - Danny Pflughoeft

3
Ich denke, Sie haben einen guten Punkt, dass der 1D-Fall hier etwas irreführend sein kann, weil er eine einfache Lösung hat, die sich nicht ohne weiteres auf höhere Dimensionen erstreckt. Ich denke, der 1D-Fall mit periodischen Randbedingungen (der Wrap-Around-Fall) könnte besser sein.
Tim Goodman

12

Ich musste nur bei einer Teillösung aufhören, da ich keine Zeit mehr hatte, aber hoffentlich bietet sogar diese Teillösung einige Einblicke in einen möglichen Ansatz zur Lösung dieses Problems.

Wenn ich mit einem schwierigen Problem konfrontiert werde, stelle ich mir gerne einfachere Probleme vor, um eine Intuition über den Problemraum zu entwickeln. Hier war der erste Schritt, den ich unternahm, dieses 2-D-Problem in ein 1-D-Problem zu reduzieren. Betrachten Sie eine Linie:

0 4 2 1 3 0 1

Irgendwie wissen Sie, dass Sie 4viermal an oder um die Stelle bombardieren müssen, um sie auf 0 zu bringen. Da links von der Stelle eine niedrigere Zahl ist, hat es keinen Vorteil, die 0oder die 4Überbombe zu bombardieren 2. Tatsächlich glaube ich (aber es fehlt ein strenger Beweis), dass das Bombardieren, 2bis der 4Punkt auf 0 fällt, mindestens so gut ist wie jede andere Strategie, um das 4auf 0 zu bringen. Man kann in einer Strategie von links nach rechts vorgehen so was:

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

Ein paar Beispiele für Bombenanschläge:

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

Die Idee, mit einer Zahl zu beginnen, die auf die eine oder andere Weise sinken muss, ist ansprechend, weil es plötzlich möglich wird, eine Lösung zu finden, die behauptet, mindestens so gut zu sein wie alle anderen Lösungen.

Der nächste Schritt in der Komplexität, bei dem diese Suche nach mindestens ebenso guten Ergebnissen noch möglich ist, befindet sich am Rande des Bretts. Mir ist klar, dass es niemals einen strengen Vorteil gibt, die Außenkante zu bombardieren. Du bist besser dran, die Stelle zu bombardieren und drei weitere Plätze kostenlos zu bekommen. In Anbetracht dessen können wir sagen, dass das Bombardieren des Rings innerhalb der Kante mindestens so gut ist wie das Bombardieren der Kante. Darüber hinaus können wir dies mit der Intuition kombinieren, dass das Bombardieren des rechten innerhalb der Kante tatsächlich der einzige Weg ist, Kantenabstände auf 0 zu reduzieren. Darüber hinaus ist es trivial einfach, die optimale Strategie herauszufinden (insofern, als sie sich befindet) mindestens so gut wie jede andere Strategie), um Ecknummern auf 0 zu bringen. Wir setzen dies alles zusammen und können einer Lösung im 2D-Raum viel näher kommen.

Angesichts der Beobachtung von Eckstücken können wir mit Sicherheit sagen, dass wir die optimale Strategie kennen, um von jedem Startbrett zu einem Brett mit Nullen an allen Ecken zu gelangen. Dies ist ein Beispiel für eine solche Karte (ich habe die Zahlen von den beiden linearen Karten oben ausgeliehen). Ich habe einige Leerzeichen anders beschriftet und erkläre, warum.

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

Man wird in der oberen Zeile bemerkt wirklich ähnelt das lineare Beispiel haben wir bereits gesehen haben. Wir erinnern an unsere frühere Beobachtung, dass der optimale Weg, um die oberste Reihe auf 0 zu bringen, darin besteht, die zweite Reihe (die xReihe) zu bombardieren . Es gibt keine Möglichkeit, die oberste Reihe durch Bombardierung einer der yReihen zu räumen, und es gibt keinen zusätzlichen Vorteil, die oberste Reihe zu bombardieren, anstatt den entsprechenden Platz in der xReihe zu bombardieren .

Wir könnten die lineare Strategie von oben anwenden (die entsprechenden Felder in der xReihe bombardieren ) und uns nur mit der obersten Reihe und sonst nichts befassen. Es würde ungefähr so ​​aussehen:

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

Der Fehler in diesem Ansatz wird in den letzten beiden Bombenanschlägen sehr deutlich. Es ist klar, dass die einzigen Bombenstellen, die die 4Zahl in der ersten Spalte in der zweiten Reihe reduzieren, die erste xund die y. Die letzten beiden Bombenanschläge sind deutlich schlechter als nur die erste x, die genau das Gleiche getan hätte (in Bezug auf den ersten Punkt in der obersten Reihe, den wir auf keine andere Weise beseitigen können). Da wir gezeigt haben, dass unsere derzeitige Strategie nicht optimal ist, ist eine Änderung der Strategie eindeutig erforderlich.

An diesem Punkt kann ich einen Schritt zurück in die Komplexität gehen und mich nur auf eine Ecke konzentrieren. Betrachten wir diesen:

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

Es ist der einzige Weg , löschen , die Räume mit bekommen 4bis auf Null sind eine Kombination aus bombardieren x, yund z. Bei einigen Akrobatik in meinem Kopf, ich bin ziemlich sicher , dass die optimale Lösung zu bombardieren ist xdreimal und dann adann b. Jetzt geht es darum herauszufinden, wie ich zu dieser Lösung gekommen bin und ob sich daraus eine Intuition ergibt, mit der wir dieses lokale Problem sogar lösen können. Ich bemerke, dass es keine Bombenangriffe yund zRäume gibt. Der Versuch, eine Ecke zu finden, in der es Sinn macht, diese Räume zu bombardieren, ergibt eine Ecke, die so aussieht:

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

In diesem Fall ist mir klar, dass die optimale Lösung darin besteht, yfünfmal und zfünfmal zu bombardieren . Gehen wir noch einen Schritt weiter.

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

Hier fühlt es sich ähnlich intuitiv , dass die optimale Lösung zu bombardieren ist aund b6 - mal und dann x4 mal.

Jetzt wird es zu einem Spiel, wie man diese Intuitionen in Prinzipien verwandelt, auf denen wir aufbauen können.

Hoffentlich geht es weiter!


10

Bei aktualisierten Fragen liefert ein einfacher Greedy-Algorithmus ein optimales Ergebnis.

Wirf A [0,0] -Bomben in Zelle A [1,1], dann A [1,0] -Bomben in Zelle A [2,1] und setze diesen Vorgang nach unten fort. Um die linke untere Ecke zu reinigen, werfen Sie maximal (A [N-1,0], A [N-2,0], A [N-3,0]) Bomben auf die Zelle A [N-2,1]. Dadurch werden die ersten 3 Spalten vollständig bereinigt.

Reinigen Sie mit dem gleichen Ansatz die Spalten 3,4,5, dann die Spalten 6,7,8 usw.

Leider hilft dies nicht, eine Lösung für das ursprüngliche Problem zu finden.


Ein "größeres" Problem (ohne "nicht zunehmende" Einschränkung) kann sich als NP-hart erweisen. Hier ist eine Skizze eines Beweises.

Angenommen, wir haben ein planares Diagramm mit einem Grad von bis zu 3. Lassen Sie uns die minimale Scheitelpunktabdeckung für dieses Diagramm ermitteln. Laut Wikipedia-Artikel ist dieses Problem für planare Graphen mit einem Grad bis zu 3 NP-schwer. Dies könnte durch Reduktion von Planar 3SAT bewiesen werden. Und Härte von Planar 3SAT - durch Reduzierung von 3SAT. Beide Beweise werden in jüngsten Vorlesungen in "Algorithmic Lower Bounds" von prof. Erik Demaine (Vorlesungen 7 und 9).

Wenn wir einige Kanten des Originaldiagramms (linkes Diagramm im Diagramm) mit jeweils einer geraden Anzahl zusätzlicher Knoten teilen, sollte das resultierende Diagramm (rechtes Diagramm im Diagramm) genau die gleiche minimale Scheitelpunktabdeckung für die ursprünglichen Scheitelpunkte aufweisen. Eine solche Transformation ermöglicht das Ausrichten von Diagrammscheitelpunkten an beliebigen Positionen im Raster.

Geben Sie hier die Bildbeschreibung ein

Wenn wir Diagrammscheitelpunkte nur auf geraden Zeilen und Spalten platzieren (so dass keine zwei Kanten, die auf einen Scheitelpunkt fallen, einen spitzen Winkel bilden), fügen Sie "Einsen" überall dort ein, wo es eine Kante gibt, und fügen Sie "Nullen" in andere Gitterpositionen ein. Wir könnten jede Lösung für das ursprüngliche Problem verwenden, um eine minimale Scheitelpunktabdeckung zu finden.


Woher kommt diese Grafik von links? Entschuldigung, ich verstehe Ihre Erklärung nicht ganz!
Ryyst

1
@ryyst: Dieses Diagramm von links ist nur ein Beispiel für ein planares Diagramm. Es wird verwendet, um zu demonstrieren, wie ein planarer Graph mit einem Grad von bis zu 4 in den gitterausgerichteten Graphen und dann in die n * m-Matrix transformiert wird. Ein auf diese Matrix angewendeter "Bombenabwurf" -Algorithmus löst das Scheitelpunktabdeckungsproblem für diesen transformierten Graphen und daher für diesen "linken" Graphen.
Evgeny Kluev

Ah, ich verstehe es jetzt und ich glaube, deine Transformation ist korrekt. Vielen Dank!
Ryyst

@ EvgenyKluev, ich denke, jetzt müssen Sie beweisen, dass die Scheitelpunktabdeckung für "planare Graphen mit einem Grad bis zu 4" immer noch NP-schwer ist.
Shahbaz

@ Shahbaz: Ich fürchte, dieser Beweis wäre zu lang. Also habe ich einen Link zum Beweis hinzugefügt.
Evgeny Kluev

9

Sie können dieses Problem als ganzzahliges Programmierproblem darstellen. (Dies ist nur eine der möglichen Lösungen, um dieses Problem anzugehen.)

Punkte haben:

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

man kann 16 Gleichungen schreiben, wobei zum Beispiel für Punkt f gilt

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

minimiert über die Summe aller Indizes und der ganzzahligen Lösung.

Die Lösung ist natürlich die Summe dieser Indizes.

Dies kann weiter vereinfacht werden, indem alle xi an den Grenzen 0 gesetzt werden, sodass Sie in diesem Beispiel die 4 + 1-Gleichung haben.

Das Problem ist, dass es keinen trivialen Algorithmus zur Lösung solcher Probleme gibt. Ich bin kein Experte in diesem Bereich, aber die Lösung dieses Problems als lineare Programmierung ist NP-schwierig.


8
Alle Probleme in NP können als ganzzahlige Programmierprobleme formuliert werden, daher ist dies nicht sehr hilfreich, es sei denn, wir wissen bereits, dass das Problem NP-Complete ist
BlueRaja - Danny Pflughoeft

1
Genau. Es ist auch nicht notwendig, genaue Bewegungen zu kennen, die ausgeführt werden müssen, um zu wissen, welche Lösung vorliegt.
Luka Rahne

1
Wenn Sie die Grenze auf 0 setzen, beträgt die Anzahl der Ungleichungen immer noch 16.
darksky

9

Dies ist eine teilweise Antwort. Ich versuche, eine Unter- und Obergrenze zu finden, die die mögliche Anzahl von Bomben sein könnte.

Bei 3x3 und kleineren Platinen ist die Lösung trivial immer die Zelle mit der größten Nummer.

Bei Brettern größer als 4x4 ist die erste offensichtliche Untergrenze die Summe der Ecken:

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

Wie auch immer Sie die Bombe arrangieren, es ist unmöglich, dieses 4x4-Brett mit weniger als 2 + 1 + 6 + 4 = 13 Bomben zu räumen.

In anderen Antworten wurde erwähnt, dass es niemals schlimmer ist, die Bombe an der zweiten Ecke zu platzieren, um die Ecke zu beseitigen, als die Bombe an der Ecke selbst zu platzieren.

*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*

Wir können die Ecken auf Null setzen, indem wir Bomben auf die zweite Ecke legen, um ein neues Brett zu erhalten:

 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

So weit, ist es gut. Wir brauchen 13 Bomben, um die Ecken zu räumen.

Beachten Sie nun die unten markierten Nummern 6, 4, 3 und 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

Es gibt keine Möglichkeit, zwei dieser Zellen mit einer einzigen Bombe zu bombardieren. Daher hat sich die Mindestbombe um 6 + 4 + 3 + 2 erhöht. Wenn wir also die Anzahl der Bomben erhöhen, mit denen wir die Ecken geräumt haben, erhalten wir das Minimum Die Anzahl der für diese Karte erforderlichen Bomben beträgt 28 Bomben. Es ist unmöglich, diese Karte mit weniger als 28 Bomben zu löschen. Dies ist die Untergrenze für diese Karte.

Sie können den Greedy-Algorithmus verwenden, um eine Obergrenze festzulegen. Andere Antworten haben gezeigt, dass ein gieriger Algorithmus eine Lösung erzeugt, die 28 Bomben verwendet. Da wir bereits früher bewiesen haben, dass keine optimale Lösung weniger als 28 Bomben haben kann, sind 28 Bomben in der Tat eine optimale Lösung.

Wenn gierig und die oben erwähnte Methode zum Finden der minimalen Grenze nicht konvergiert, müssen Sie wahrscheinlich alle Kombinationen überprüfen.

Der Algorithmus zum Finden der Untergrenze ist der folgende:

  1. Wählen Sie ein Element mit der höchsten Nummer aus und nennen Sie es P.
  2. Markieren Sie alle Zellen zwei Schritte von P und P entfernt als nicht auswählbar.
  3. Fügen Sie P zur minimumsListe hinzu.
  4. Wiederholen Sie Schritt 1, bis alle Zellen nicht mehr ausgewählt werden können.
  5. Summiere die minimumsListe, um die Untergrenze zu erhalten.

9

Dies wäre ein gieriger Ansatz:

  1. Berechnen Sie eine "Score" -Matrix der Ordnung n X m, wobei Score [i] [j] der Gesamtabzug der Punkte in der Matrix ist, wenn Position (i, j) bombardiert wird. (Die maximale Punktzahl eines Punktes beträgt 9 und die minimale Punktzahl 0)

  2. Bewegen Sie sich zeilenweise und suchen Sie die erste Position mit der höchsten Punktzahl (z. B. (i, j)).

  3. Bombe (i, j). Erhöhen Sie die Anzahl der Bomben.

  4. Wenn alle Elemente der ursprünglichen Matrix nicht Null sind, gehen Sie zu 1.

Ich habe meine Zweifel, dass dies die optimale Lösung ist.

Bearbeiten:

Der oben veröffentlichte Greedy-Ansatz bietet uns wahrscheinlich nicht die optimale Lösung, obwohl er funktioniert. Also dachte ich mir, ich sollte ein paar Elemente von DP hinzufügen.

Ich denke, wir können uns darauf einigen, dass zu jedem Zeitpunkt eine der Positionen mit der höchsten "Punktzahl" (Punktzahl [i] [j] = Gesamtabzug der Punkte, wenn (i, j) bombardiert wird) als Ziel ausgewählt werden muss. Ausgehend von dieser Annahme ist hier der neue Ansatz:

NumOfBombs (M): (gibt die erforderliche Mindestanzahl an Bomben zurück)

  1. Gegeben ist eine Matrix M der Ordnung n X m. Wenn alle Elemente von M Null sind, geben Sie 0 zurück.

  2. Berechnen Sie die "Score" -Matrix M.

    Die k verschiedenen Positionen P1, P2, ... Pk (1 <= k <= n * m) seien die Positionen in M ​​mit den höchsten Punktzahlen.

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

    wobei M1, M2, ..., Mk die resultierenden Matrizen sind, wenn wir die Positionen P1, P2, ... bzw. Pk bombardieren.

Wenn wir möchten, dass die Reihenfolge der Positionen zusätzlich nuklear wird, müssen wir auch die Ergebnisse von "min" verfolgen.


3
Ich frage mich, ob die Einstellung der Punktzahl als Summe der aktuellen Werte zu besseren Ergebnissen führen würde. Das würde den Boden wesentlich effizienter abflachen.
Eugene

@ Eugene: Sehr interessanter Punkt. Ich kann mir keinen Grund
vorstellen,

@Eugene: Vielleicht könnte die Summe der aktuellen Werte in der Nähe für die "Prioritäts" -Messung verwendet werden? Nuke den Knoten mit der höchsten Punktzahl und der höchsten Priorität ..
SidR

Lesen Sie einfach diese Antwort, ich denke, sie ähnelt der zweiten Antwort, die ich gerade gepostet habe (vielleicht etwas mehr in meiner Antwort). Ich denke, es wäre optimal, wenn es immer ein einzelnes Feld mit der maximalen Punktzahl gäbe, denn Sie würden garantiert sein, dass jeder Bombenangriff den größtmöglichen Einfluss hat. Die Reihenfolge der Bombenanschläge spielt keine Rolle, daher sollte es optimal sein, bei jedem Schritt die beste zu wählen. Aber weil es Unentschieden für "Beste" geben könnte, müssten Sie für eine optimale Lösung möglicherweise beide zurückverfolgen und beide ausprobieren, wenn es ein Unentschieden gibt.
Tim Goodman

1
@ Eugene, vielleicht folge ich dir nicht. Was ist der Unterschied zwischen der größten Reduzierung und der kleinsten Summe aller verbleibenden Werte? Die Summe der verbleibenden Werte (nach dem Bombenangriff) ist nur der aktuelle Gesamtwert abzüglich der Reduzierung des Bombenangriffs auf diesen Raum. Sind diese also nicht gleichwertig?
Tim Goodman

8

Ihr neues Problem mit den nicht abnehmenden Werten in den Zeilen ist recht einfach zu lösen.

Beachten Sie, dass die linke Spalte die höchsten Zahlen enthält. Daher muss jede optimale Lösung diese Spalte zuerst auf Null reduzieren. Auf diese Weise können wir einen 1-D- Bombenangriff auf diese Säule durchführen und jedes Element darin auf Null reduzieren. Wir lassen die Bomben auf die zweite Säule fallen, damit sie maximalen Schaden anrichten. Ich denke, es gibt hier viele Beiträge, die sich mit dem 1D-Fall befassen, daher fühle ich mich sicher, diesen Fall zu überspringen. (Wenn du willst, dass ich es beschreibe, kann ich es.) Aufgrund der abnehmenden Eigenschaft werden alle drei Spalten ganz links auf Null reduziert. Wir werden hier jedoch nachweislich eine Mindestanzahl von Bomben verwenden, da die linke Spalte auf Null gesetzt werden muss.

Sobald die linke Spalte auf Null gesetzt ist, schneiden wir einfach die drei Spalten ganz links ab, die jetzt auf Null gesetzt sind, und wiederholen sie mit der jetzt reduzierten Matrix. Dies muss uns eine optimale Lösung geben, da wir in jeder Phase eine nachweislich minimale Anzahl von Bomben einsetzen.


Ich verstehe es. Ich dachte an eine ähnliche Idee. : S Das nächste Mal werde ich genauer lesen. Aber dank dessen haben viele Leute ein "nettes" Problem zu lösen.
abc

4

Mathematica Integer Linear Programming mit Branch-and-Bound

Wie bereits erwähnt, kann dieses Problem mit einer ganzzahligen linearen Programmierung (die NP-hart ist ) gelöst werden . In Mathematica ist ILP bereits integriert. "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."[Siehe Tutorial zur eingeschränkten Optimierung in Mathematica.]

Ich habe den folgenden Code geschrieben, der ILP-Bibliotheken von Mathematica verwendet. Es ist überraschend schnell.

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}]]];]

Für das im Problem bereitgestellte Beispiel:

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]

Ausgänge

Geben Sie hier die Bildbeschreibung ein

Für alle, die dies mit einem gierigen Algorithmus lesen

Probieren Sie Ihren Code für das folgende 10x10-Problem aus:

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

Hier ist es durch Kommas getrennt:

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

Für dieses Problem enthält meine Lösung 208 Bomben. Hier ist eine mögliche Lösung (ich konnte dies in ca. 12 Sekunden lösen).

Geben Sie hier die Bildbeschreibung ein

Um die Ergebnisse zu testen, die Mathematica liefert, prüfen Sie, ob Ihr gieriger Algorithmus es besser kann.



3

Es besteht keine Notwendigkeit, das Problem in lineare Unterprobleme umzuwandeln.

Verwenden Sie stattdessen eine einfache gierige Heuristik, die die Ecken bombardiert , beginnend mit der größten.

Im angegebenen Beispiel gibt es vier Ecken, {2, 1, 6, 4}. Für jede Ecke gibt es keinen besseren Zug, als die Zellendiagonale zur Ecke zu bombardieren. Wir wissen also, dass unsere ersten 2 + 1 + 6 + 4 = 13 Bombenangriffe in diesen diagonalen Zellen stattfinden müssen. Nach dem Bombenangriff bleibt uns eine neue Matrix:

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 

Nach den ersten 13 Bombenanschlägen verwenden wir die Heuristik, um 3 0 2 über drei Bombenanschläge zu eliminieren. Jetzt haben wir 2 neue Ecken, {2, 1} in der 4. Reihe. Wir bombardieren diese, weitere 3 Bombenanschläge. Wir haben die Matrix jetzt auf 4 x 4 reduziert. Es gibt eine Ecke oben links. Wir bombardieren das. Jetzt haben wir noch 2 Ecken, {5, 3}. Da 5 die größte Ecke ist, bombardieren wir diese zuerst, 5 Bomben, und schließlich die 3 in der anderen Ecke. Die Summe beträgt 13 + 3 + 3 + 1 + 5 + 3 = 28.


1
Ich verstehe nicht, was Sie im Allgemeinen tun, nachdem Sie Ecken bombardiert haben
RiaD

Das Bombardieren der Ecke ist niemals effektiver als das diagonale Bombardieren von der Ecke nach innen.
Psr

1
psr du missverstehst meinen Beitrag, ich bombardiere diagonal aus der Ecke, lies den Beitrag noch einmal
Tyler Durden

11
@ TylerDurden: Dies funktioniert nur, weil die Matrix klein ist. Bei größeren Matrizen können Sie nach dem Bombardieren der Ecke im Allgemeinen die Kanten nicht mehr schneiden.
Lie Ryan

3

Dies führt eine breite Suche nach dem kürzesten Weg (einer Reihe von Bombenanschlägen) durch dieses "Labyrinth" von Positionen durch. Nein, ich kann leider nicht beweisen, dass es keinen schnelleren Algorithmus gibt.

#!/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
Dieser Algorithmus findet die geringste Anzahl von Zügen, kann jedoch sehr lange dauern. Haben Sie dies für den angegebenen Datensatz ausgeführt? Dies würde eine Basis für andere Algorithmen zum Vergleich geben.
Ryan Amos

1
Eine Teilmenge von 5x4 der gegebenen Matrix wurde in ungefähr 2 Sekunden gelöst, 5x5 dauerte bereits über 2 Minuten. Ich habe noch nicht mehr versucht ;-) Ja, dieser Algorithmus ist nur für die ursprüngliche Aufgabe optimiert: Finde die kürzeste Lösung.
Alfe

2
Das ist das Schöne an exponentieller Komplexität.
Ryan Amos

3

Es scheint, dass ein linearer Programmieransatz hier sehr hilfreich sein kann.

Sei P m xn die Matrix mit den Werten der Positionen:

Matrix von Positionen

Definieren wir nun eine Bombenmatrix B (x, y) mxn mit 1 ≤ x ≤ m , 1 ≤ y ≤ n wie folgt

Bombenmatrix

Sodass

Werte von Positionen in der Bombenmatrix

Beispielsweise:

B (3, 3)

Wir suchen also nach einer Matrix B m xn = [ b ij ], die

  1. Kann als Summe von Bombenmatrizen definiert werden:

    B als Summe der Bombenmatrizen

    ( q ij wäre dann die Menge der Bomben, die wir in Position p ij fallen lassen würden )

  2. p ij - b ij ≤ 0 (um prägnanter zu sein, sagen wir es als P - B ≤ 0 )

Außerdem sollte B die Summe minimieren Summe der Bombenmengen.

Wir können auch B als die hässliche Matrix vor uns schreiben :

B als Matrix der Mengenmenge

und da P - B ≤ 0 (was P ≤ B bedeutet ) haben wir das folgende ziemlich lineare Ungleichungssystem unten:

Beziehung zwischen der Anzahl der abgeworfenen Bomben und den Werten in Positionen

Als q mn x 1 definiert als

Vektor der Mengen

p mn x 1 definiert als

Werte von P als Vektor verteilt

Wir können sagen, wir haben ein System Das folgende System wird als Produkt von Matrizen dargestellt: http://latex.codecogs.com/gif.download?S%5Cmathbf%7Bq%7D&space;%5Cge&space;%5Cmathbf%7Bp%7D ist S mn x In der Matrix muss umgekehrt werden, um das System zu lösen. Ich habe es nicht selbst erweitert, aber ich glaube, es sollte einfach sein, es im Code zu tun.

Jetzt haben wir ein minimales Problem, das als angegeben werden kann

Das System müssen wir lösen

Ich glaube, es ist etwas Leichtes, fast Triviales, mit so etwas wie dem Simplex-Algorithmus gelöst zu werden (es gibt dieses ziemlich coole Dokument darüber ). Ich kenne jedoch fast keine lineare Programmierung (ich werde einen Kurs darüber auf Coursera belegen, aber es ist erst in der Zukunft ...), ich hatte einige Kopfschmerzen beim Versuch, es zu verstehen, und ich habe einen riesigen freiberuflichen Job zu erledigen, also habe ich gib einfach hier auf. Es kann sein , dass ich etwas falsch an einem gewissen Punkt hat, oder dass es nicht weiter gehen können, aber ich glaube , dieser Weg schließlich führen kann die Lösung. Wie auch immer, ich bin gespannt auf Ihr Feedback.

(Besonderer Dank für diese erstaunliche Website zum Erstellen von Bildern aus LaTeX-Ausdrücken )


Sind Sie sicher, dass Ihre Ungleichungen nicht umgekehrt werden? Das ist Sq> = P? Das heißt, die Gesamtzahl der Bombenangriffe auf ein Quadrat ist größer oder gleich der angegebenen Matrix.
Darksky

1
Wenn die Variablen eines linearen Programms auf ganze Zahlen beschränkt sind, nennen wir das "ganzzahlige lineare Programmierung" (IP). Im Gegensatz zum kontinuierlichen Fall ist IP NP-vollständig. Leider hilft der Simplex-Algorithmus nicht, es sei denn, eine Annäherung ist akzeptabel. Und IP wurde bereits in einer anderen Antwort erwähnt .
BlueRaja - Danny Pflughoeft

@ BlueRaja-DannyPflughoeft richtig. "Despite the many crucial applications of this problem, and intense interest by researchers, no efficient algorithm is known for it.siehe Seite 254. Die ganzzahlige lineare Programmierung ist ein sehr schwieriges Rechenproblem. Unsere einzige Hoffnung, effizient zu sein, besteht darin, die intrinsischen Eigenschaften Ihrer Matrix S auszunutzen. Es ist schließlich nicht so willkürlich.
Darksky

3

Diese gierige Lösung scheint richtig zu sein :

Wie in den Kommentaren erwähnt, schlägt dies in 2D fehl. Aber vielleicht können Sie es verbessern.

Für 1D:
Wenn es mindestens 2 Zahlen gibt, müssen Sie nicht ganz links schießen, da das Schießen auf die zweite nicht schlechter ist . Schießen Sie also auf die Sekunde, während die erste nicht 0 ist, weil Sie es tun müssen. Gehen Sie zur nächsten Zelle. Vergiss die letzte Zelle nicht.

C ++ - Code:

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])
    }
}

Also für 2D:
Nochmals: Sie müssen nicht in der ersten Reihe schießen (wenn es die zweite gibt). Also schieß zum zweiten. Lösen Sie die 1D-Aufgabe für die erste Zeile. (weil Sie es null machen müssen). Gehen. Letzte Reihe nicht vergessen.


5
Ein Gegenbeispiel : "0110","1110","1110". Sie brauchen nur 1 Schuss, aber ich glaube, Ihr Algorithmus würde 2 verwenden.
Maniek

2

Um die Anzahl der Bomben zu minimieren, müssen wir die Wirkung jeder Bombe maximieren. Um dies zu erreichen, müssen wir bei jedem Schritt das beste Ziel auswählen. Für jeden Punkt, der es summiert, und seine acht Nachbarn - könnte als Effizienzmaß für die Bombardierung dieses Punktes verwendet werden. Dies liefert eine nahezu optimale Reihenfolge der Bomben.

UPD : Wir sollten auch die Anzahl der Nullen berücksichtigen, da die Kombination dieser Nullen ineffizient ist. Tatsächlich besteht das Problem darin, die Anzahl der geschlagenen Nullen zu minimieren. Aber wir können nicht wissen, wie ein Schritt uns diesem Ziel näher bringt. Ich stimme der Idee zu, dass das Problem NP-vollständig ist. Ich schlage einen gierigen Ansatz vor, der eine Antwort gibt, die der Realität nahe kommt.


Das ist nicht optimal. Gegenbeispiel: 1010101, 0010100(obere Reihe, untere Reihe) Ihr Ansatz 3. benötigen Sie kann in 2 durchgeführt werden
Mysticial

2

Ich glaube, um die Anzahl der Bomben zu minimieren, müssen Sie einfach die Schadensmenge maximieren. Dazu müssen Sie den Bereich mit der stärksten Kraft überprüfen. Analysieren Sie also zuerst das Feld mit einem 3x3-Kernel und überprüfen Sie, wo sich die Summe befindet ist stärker .. und Bombe dort .. und tun, bis das Feld flach ist .. für dieses Feld ist die Antwort 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');

Dies ist der gleiche Algorithmus wie bei einigen anderen Antworten, jedoch viel später.
Psr

@psr Nicht nur das. Es ist nicht optimal.
Mysticial

Ich habe es gepostet, weil ich, während dieser Algorithmus vorgeschlagen wurde, keinen Code-Post oder "Prof of Concept" gefunden habe. also dachte ich, es könnte der Diskussion helfen .. aber .. übrigens @Mysticial haben Sie prof, dass es einen optimaleren Weg gibt?
CaldasGSM

@CaldasGSM Keine Sorge, das ursprüngliche Problem (ohne die Sequenzierung) ist schwierig. Bisher gibt es nur eine Antwort , die es optimal löst, aber sie läuft in exponentieller Zeit.
Mysticial

2

Hier ist eine Lösung, die die guten Eigenschaften der Ecken verallgemeinert.

Nehmen wir an, wir könnten einen perfekten Ablagepunkt für ein bestimmtes Feld finden, dh einen besten Weg, um den Wert darin zu verringern. Um dann die Mindestanzahl der abzuwerfenden Bomben zu ermitteln, könnte ein erster Entwurf eines Algorithmus erstellt werden (der Code wird aus einer Ruby-Implementierung kopiert):

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

Die Herausforderung ist choose_a_perfect_drop_point. Definieren wir zunächst, was ein perfekter Ablagepunkt ist.

  • Ein Drop-Point für (x, y)verringert den Wert in(x, y) . Es kann auch Werte in anderen Zellen verringern.
  • Ein Abwurfpunkt a für (x, y)ist besser als ein Abwurfpunkt b für, (x, y)wenn er die Werte in einer geeigneten Obermenge der Zellen verringert , für die b abnimmt.
  • Ein Abwurfpunkt ist maximal, wenn es keinen anderen besseren Abwurfpunkt gibt.
  • Zwei Ablagepunkte für (x, y)sind äquivalent, wenn sie denselben Satz von Zellen verringern.
  • Ein Drop-Point für (x, y)ist perfekt, wenn er allen maximalen Drop-Punkten für entspricht (x, y).

Wenn es einen perfekten Abwurfpunkt für gibt (x, y), können Sie den Wert nicht (x, y)effektiver verringern, als eine Bombe auf einen der perfekten Abwurfpunkte für zu werfen(x, y) .

Ein perfekter Drop-Point für ein bestimmtes Feld ist ein perfekter Drop-Point für eine seiner Zellen.

Hier einige Beispiele:

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

Der perfekte Ablagepunkt für die Zelle (0, 0)(nullbasierter Index) ist (1, 1). Alle anderen Punkte abgezogen (1, 1), das heißt (0, 0), (0, 1)und (1, 0), weniger Zellen verringern.

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

Ein perfekter Einstichpunkt für die Zelle (2, 2)(Null-Basis - Index) ist (2, 2), und auch alle umgebenden Zellen (1, 1), (1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2), und (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

Ein perfekter Abwurfpunkt für die Zelle (2, 2)ist (3, 1): Er verringert den Wert in (2, 2)und den Wert in (4, 0). Alle anderen Abwurfpunkte für (2, 2)sind nicht maximal, da sie eine Zelle weniger verringern. Der perfekte Drop-Point für (2, 2)ist auch der perfekte Drop-Point für (4, 0)und der einzige perfekte Drop-Point für das Feld. Dies führt zur perfekten Lösung für dieses Feld (ein Bombenabwurf).

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

Es gibt keinen perfekten Abwurfpunkt für (2, 2): Beide (1, 1)und (1, 3)Abnehmen (2, 2)und eine andere Zelle (sie sind maximale Abwurfpunkte für (2, 2)), aber sie sind nicht gleichwertig. Ist (1, 1)jedoch ein perfekter Ablagepunkt für (0, 0)und (1, 3)ist ein perfekter Ablagepunkt für (0, 4).

Mit dieser Definition perfekter Abwurfpunkte und einer bestimmten Reihenfolge von Überprüfungen erhalte ich das folgende Ergebnis für das Beispiel in der Frage:

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

Der Algorithmus funktioniert jedoch nur, wenn nach jedem Schritt mindestens ein perfekter Abwurfpunkt vorhanden ist. Es ist möglich, Beispiele zu konstruieren, bei denen es keine perfekten Abwurfpunkte gibt:

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

In diesen Fällen können wir den Algorithmus so ändern, dass wir anstelle eines perfekten Abwurfpunkts eine Koordinate mit einer minimalen Auswahl an maximalen Abwurfpunkten auswählen und dann das Minimum für jede Auswahl berechnen. Im obigen Fall haben alle Zellen mit Werten zwei maximale Abwurfpunkte. Hat zum Beispiel (0, 1)die maximalen Abwurfpunkte (1, 1)und (1, 2). Die Auswahl einer der beiden Optionen und die anschließende Berechnung des Minimums führt zu folgendem Ergebnis:

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

Dies ist so ziemlich der oben vorgestellte gierige Algorithmus.
Darksky

Nun, es ist auch ein gieriger Algorithmus, aber anstatt mich auf Ecken und Kanten zu konzentrieren, habe ich definiert, wie der nächste Ablagepunkt ausgewählt werden soll. Mit dem Beispielquadrat von 5x7 ist es einfach, über Ecken auf einem 1000x1000-Feld zu sprechen, nicht so sehr. Wenn Sie die Reihenfolge überprüfen, in der mein Algorithmus das Feld löscht, erfolgt dies nicht von außen nach innen, sondern von oben nach unten / von links nach rechts.
Tammo Freese

2

Hier ist eine andere Idee:

Beginnen wir damit, jedem Feld auf dem Brett ein Gewicht zuzuweisen, wie viele Zahlen durch das Abwerfen einer Bombe reduziert würden. Wenn der Raum also eine Zahl ungleich Null hat, erhält er einen Punkt, und wenn ein angrenzender Raum eine Zahl ungleich Null hat, erhält er einen zusätzlichen Punkt. Wenn es also ein 1000-mal-1000-Raster gibt, wird jedem der 1 Million Felder ein Gewicht zugewiesen.

Sortieren Sie dann die Liste der Felder nach Gewicht und bombardieren Sie das Feld mit dem höchsten Gewicht. Dies ist sozusagen das Beste für unser Geld.

Aktualisieren Sie danach das Gewicht jedes Feldes, dessen Gewicht von der Bombe beeinflusst wird. Dies ist der Raum, den Sie bombardiert haben, und jeder Raum unmittelbar daneben und jeder Raum unmittelbar daneben. Mit anderen Worten, jeder Raum, dessen Wert durch die Bombardierung auf Null hätte reduziert werden können, oder der Wert eines benachbarten Raums, der auf Null reduziert werden könnte.

Sortieren Sie dann die Listenbereiche nach Gewicht neu. Da nur eine kleine Teilmenge der Felder durch die Bombardierung ihr Gewicht geändert hat, müssen Sie nicht die gesamte Liste neu erstellen, sondern nur diese in der Liste verschieben.

Bombardieren Sie den neuen Raum mit dem höchsten Gewicht und wiederholen Sie den Vorgang.

Dies garantiert, dass jeder Bombenangriff so viele Felder wie möglich reduziert (im Grunde trifft er so wenige Felder, die bereits Null sind, wie möglich), so dass es optimal wäre, außer dass es sich um Gewichte handeln kann. Daher müssen Sie möglicherweise eine Rückverfolgung durchführen, wenn es eine Krawatte für das Spitzengewicht gibt. Es kommt jedoch nur auf ein Unentschieden für das Spitzengewicht an, nicht auf andere Unentschieden. Hoffentlich ist es nicht zu viel Rückverfolgung.

Bearbeiten: Das folgende Gegenbeispiel von Mysticial zeigt, dass dies tatsächlich nicht garantiert optimal ist, unabhängig von Gewichtsbindungen. In einigen Fällen, wenn Sie das Gewicht in einem bestimmten Schritt so weit wie möglich reduzieren, sind die verbleibenden Bomben tatsächlich zu weit verteilt, um nach dem zweiten Schritt eine so hohe kumulative Reduzierung zu erzielen, wie Sie es mit einer etwas weniger gierigen Wahl im ersten Schritt hätten tun können. Ich war etwas irregeführt von der Vorstellung, dass die Ergebnisse unempfindlich gegenüber der Reihenfolge der Bombenanschläge sind. Sie sindunempfindlich gegenüber der Reihenfolge, da Sie jede Reihe von Bombenangriffen von Anfang an in einer anderen Reihenfolge wiederholen und am Ende das gleiche Ergebnis erhalten können. Daraus folgt jedoch nicht, dass Sie jeden Bombenanschlag einzeln betrachten können. Zumindest muss jeder Bombenanschlag so betrachtet werden, dass berücksichtigt wird, wie gut er das Brett für nachfolgende Bombenangriffe vorbereitet.


Es wird immer noch viel Backtracking geben, da die Felder zu Beginn sehr wenig Null haben, werden die Gewichte der meisten Zellen alle Neun sein.
Lie Ryan

Ja, das ist ein guter Punkt, da die möglichen Gewichte keinen großen Bereich haben (nur 0 bis 9).
Tim Goodman

Ich bin mir immer noch nicht 100% sicher, wie notwendig das Backtracking ist ... es könnte lehrreich sein, ein Raster zu erstellen, in dem eine Wahl der gierigen Bomben einer anderen Wahl der gierigen Bomben unterlegen ist. Vielleicht gibt es eine konsequente Möglichkeit, vorauszusehen, welche besser ist.
Tim Goodman

Eigentlich sehe ich, dass Colonel Panic dies in seiner Antwort getan hat. Der Grund, warum eine gierige Wahl besser sein kann als eine andere, ist, dass man die verbleibenden Zahlen weiter verteilt lässt.
Tim Goodman

3
1010101, 0010100Könnte ein Gegenbeispiel sein , die diesen Ansatz erweist sich als nicht optimal. Dieser Ansatz erfordert 3. Es kann in 2.
Mysticial

1

Nehmen wir an, wir nummerieren die Boardpositionen 1, 2, ..., nx m. Jede Folge von Bombenabwürfen kann durch eine Folge von Zahlen in diesem Satz dargestellt werden, wobei sich Zahlen wiederholen können. Der Effekt auf dem Brett ist jedoch der gleiche, unabhängig davon, in welcher Reihenfolge Sie die Bomben abwerfen. Daher kann wirklich jede Auswahl an Bombenabwürfen als Liste von nxm-Zahlen dargestellt werden, wobei die erste Zahl die Anzahl der auf Position 1 abgeworfenen Bomben darstellt Die zweite Zahl gibt die Anzahl der Bomben an, die auf Position 2 usw. abgeworfen wurden. Nennen wir diese Liste der nxm-Zahlen den "Schlüssel".

Sie können versuchen, zuerst alle Board-Zustände zu berechnen, die sich aus einem Bombenabwurf ergeben, und dann alle Board-Zustände zu berechnen, die sich aus 2 Bombenabwürfen usw. ergeben, bis Sie alle Nullen erhalten. Bei jedem Schritt würden Sie die Zustände mit dem oben definierten Schlüssel zwischenspeichern, damit Sie diese Ergebnisse bei der Berechnung des nächsten Schritts verwenden können (ein "dynamischer Programmier" -Ansatz).

Abhängig von der Größe von n, m und den Zahlen im Raster kann der Speicherbedarf dieses Ansatzes jedoch zu hoch sein. Sie können alle Ergebnisse für N Bombenabwürfe wegwerfen, sobald Sie alle Ergebnisse für N + 1 berechnet haben. Dort gibt es also einige Einsparungen. Und natürlich können Sie nichts zwischenspeichern, wenn es viel länger dauert - der dynamische Programmieransatz tauscht Speicher gegen Geschwindigkeit.


1
Zweifel, dass es seitdem möglich ist (wenn ich dich richtig verstanden habe). n = m. Ich brauche 10 ^ 6 int Zeiger auf (10 ^ 6) ^ 2 int Zellen. Ich habe so viele Bretter wie Schlüssel in der Tabelle. 10 ^ 12 Zweifel, ich kann so viel in 32-Bit-Maschine zuordnen.
abc

Ja, ich habe gerade Ihren Kommentar gesehen, dass Bretter bis zu 1000 mal 1000 groß sind. Das sind also eine Million Zoll für den Zustand jedes Bretts plus eine Million Zoll für die Anzahl der Bomben, die auf jede Position abgeworfen wurden. Für jedes Board, das Sie speichern, benötigen Sie 2 Millionen Zoll, und es gibt viele mögliche Boards ...
Tim Goodman

Ich habe eine zweite Antwort hinzugefügt, die einen anderen Ansatz verwendet.
Tim Goodman

1
Ja. Eine Art Brute-Force-Ansatz, aber ich denke, für ein großes Board nicht sehr praktisch.
Tim Goodman

@Kostek, warum so eine niedrige Schätzung? Es ist eher ein k ^ (m * n) Speicher, wobei k die Grenze für die Zahlen ist, mit denen die Karte anfänglich gefüllt ist.
Rotsor

1

Wenn Sie die absolut optimale Lösung zum Reinigen des Boards wünschen, müssen Sie klassisches Backtracking verwenden. Wenn die Matrix jedoch sehr groß ist, dauert es ewig, bis die beste Lösung gefunden ist. Wenn Sie eine "mögliche" optimale Lösung wünschen, können Sie den Greedy-Algorithmus verwenden Wenn Sie Hilfe beim Schreiben des Algorithmus benötigen, kann ich Ihnen helfen

Kommen Sie und denken Sie daran, das ist der beste Weg. Erstellen Sie dort eine weitere Matrix, in der Sie die Punkte speichern, die Sie entfernen, indem Sie dort eine Bombe ablegen. Wählen Sie dann die Zelle mit den maximalen Punkten aus und lassen Sie die Bombe dort fallen. Aktualisieren Sie die Punktematrix und fahren Sie fort. Beispiel:

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))

Zellenwert +1 für jede benachbarte Zelle mit einem Wert höher als 0


7
werden müssen klassische Rückzieher verwenden . Hast du einen Beweis dafür?
Shahbaz

Ich bin mir nicht sicher. Es ist von einem Wettbewerb, auf den ich mich vorbereite (aus dem Vorjahr). Die Grenzwerte sind 1 <= n, m <= 1000 (weiß nicht, ob groß oder nicht). Auf jeden Fall brauchen Sie eine genaue Antwort (ähnlich wie beim CERC-Wettbewerb und so weiter). Es gibt kein Zeitlimit, keine Antworten, auch keine Lösungen auf der Wettbewerbsseite.
abc

Nun, jeder andere Algorithmus gibt Ihnen eine mögliche optimale Lösung, aber bis Sie alle ausprobieren (Backtracking), werden Sie nicht wissen, ob diese Lösung die beste ist
cosmin.danisor

2
Sie müssen kein Backtracking verwenden, da es sich um eine Kombination handelt, die Sie suchen, nicht um Permutaion. Die Reihenfolge des Abwerfens von Bomben ist nicht wichtig
Luka Rahne

dann können Sie versuchen, eine Variante von gierig zu verwenden. Erstellen Sie bei jedem Schritt eine neue Matrix und jeder Punkt hat den Wert seiner Zelle + 1 für jede Zelle daneben> 0. Auf diese Weise wird besser ausgewählt, wo die nächsten Bomben abgeworfen werden sollen
cosmin.danisor

1

Rohe Gewalt !

Ich weiß, dass es nicht effizient ist, aber selbst wenn Sie einen schnelleren Algorithmus finden, können Sie immer anhand dieses Ergebnisses testen, wie genau es ist.

Verwenden Sie eine Rekursion wie folgt:

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);

}

Sie können dies durch Zwischenspeichern effizienter gestalten. Wenn unterschiedliche Methoden zu demselben Ergebnis führen, sollten Sie nicht dieselben Schritte wiederholen.

Um dies zu erläutern:

Wenn die Bombardierung der Zelle 1,3,5 zum gleichen Ergebnis führt wie die Bombardierung der Zelle 5,3,1, sollten Sie in beiden Fällen nicht alle nächsten Schritte erneut ausführen. Nur 1 ist ausreichend. Sie sollten alle irgendwo speichern Tabelle gibt an und verwendet die Ergebnisse.

Ein Hash von Tabellenstatistiken kann verwendet werden, um einen schnellen Vergleich durchzuführen.


1
  1. Bombardieren Sie niemals die Grenze (es sei denn, das Quadrat hat keinen nicht grenzüberschreitenden Nachbarn).
  2. Null Ecke.
  3. Um die Ecke auf Null zu setzen, lassen Sie den Wert der Ecke ein Quadrat entfernt diagonal fallen (der einzige nicht grenzüberschreitende Nachbar).
  4. Dadurch entstehen neue Ecken. Gehe zu 2

Bearbeiten: Ich habe nicht bemerkt, dass Kostek fast den gleichen Ansatz vorgeschlagen hat, daher behaupte ich jetzt stärker: Wenn zu löschende Ecken so gewählt werden, dass sie sich immer auf der äußersten Ebene befinden, ist dies optimal.

Im Beispiel von OP: Wenn Sie 2 (als 1 + 1 oder 2) auf etwas anderes als auf 5 fallen lassen, wird kein Quadrat getroffen, das auf 5 fallen würde. Also müssen wir einfach 2 auf 5 fallen lassen (und 6 auf links unten 1 ...)

Danach gibt es nur noch eine Möglichkeit, die Ecke (oben links) zu löschen, die ursprünglich 1 (jetzt 0) war, und zwar durch Ablegen von 0 auf B3 (Excel-ähnliche Notation). Und so weiter.

Beginnen Sie erst nach dem Löschen ganzer A- und E-Spalten sowie 1 und 7 Zeilen mit dem Löschen einer Ebene tiefer.

Betrachten Sie das Löschen nur der absichtlich gelöschten. Das Löschen von Ecken mit 0 Werten kostet nichts und vereinfacht das Nachdenken darüber.

Da alle auf diese Weise abgeworfenen Bomben abgeworfen werden müssen und dies zu geräumten Feldern führt, ist dies eine optimale Lösung.


Nach einem guten Schlaf wurde mir klar, dass dies nicht stimmt. Erwägen

  ABCDE    
1 01000
2 10000
3 00000
4 00000

Mein Ansatz würde Bomben auf B3 und C2 abwerfen, wenn das Abwerfen auf B2 ausreichen würde


Aber ist das aber optimal?
Mysticial

7
Neue Ecken können auf zwei Arten bombardiert werden (wenn der größte Eckpunkt den niedrigsten aller 4 Werte enthält). Welches ist das optimale Bombardement?
abc

Ich habe über einen ähnlichen Ansatz nachgedacht, und wenn Sie eine Situation erreichen, wie sie Kostek beschrieben hat, beginnen Sie mit dem Backtracking ...
Karoly Horvath

Ecken geben Ihnen minimale Mengen an Bomben, die auf ein diagonales Quadrat fallen gelassen werden müssen. Sobald Sie sie auf Null gesetzt haben, hat die nächste Randkachel nicht unbedingt einen offensichtlichen optimalen Punkt. Dies ist jedoch eine gute Möglichkeit, den Suchraum zu reduzieren.
Eugene

Was ist mit der Auswahl der neuen Eckdiagonale, die die höchste Gesamtzahl in der Trefferbox ergibt?
Richter Maygarden

1

Hier ist meine Lösung. Ich werde es noch nicht in Code schreiben, da ich keine Zeit habe, aber ich glaube, dass dies jedes Mal eine optimale Anzahl von Zügen erzeugen sollte - obwohl ich nicht sicher bin, wie effizient es sein würde, es zu finden die Punkte zu bombardieren.

Erstens, wie @Luka Rahne in einem der Kommentare feststellte, ist die Reihenfolge, in der Sie bombardieren, nicht wichtig - nur die Kombination.

Zweitens ist, wie viele andere angegeben haben, das Bombardieren der Diagonale von den Ecken aus optimal, da es mehr Punkte als die Ecken berührt.

Dies bildet die Grundlage für meine Version des Algorithmus: Wir können das "1-off von den Ecken" zuerst oder zuletzt bombardieren, es spielt keine Rolle (theoretisch). Wir bombardieren diese zuerst, weil es spätere Entscheidungen erleichtert (in der Praxis). Wir bombardieren den Punkt, der die meisten Punkte betrifft, und bombardieren gleichzeitig diese Ecken.

Definieren wir Widerstandspunkte als die Punkte auf dem Brett mit den nicht bombardierbarsten Punkten + der größten Anzahl von Nullen um sie herum

Nicht bombardierbare Punkte können als Punkte definiert werden, die in unserem aktuellen Geltungsbereich nicht existieren des Boards, das wir betrachten, sind.

Ich werde auch 4 Grenzen definieren, die unseren Bereich behandeln: Oben = 0, Links = 0, Unten = k, rechts = j. (Werte zum Starten)

Schließlich definiere ich optimale Bomben als Bomben, die auf Punkte abgeworfen werden, die an Widerstandspunkte angrenzen und (1) den höchstwertigen Widerstandspunkt und (2) die größtmögliche Anzahl von Punkten berühren.

In Bezug auf den Ansatz ist es offensichtlich, dass wir von außen nach innen arbeiten. Wir werden in der Lage sein, gleichzeitig mit 4 'Bombern' zu arbeiten.

Die ersten Widerstandspunkte sind offensichtlich unsere Ecken. Die "out of bound" -Punkte sind nicht bombardierbar (es gibt 5 Punkte außerhalb des Bereichs für jede Ecke). Also bombardieren wir die Punkte diagonal zuerst an den Ecken.

Algorithmus:

  1. Finde die 4 optimalen Bombenpunkte.
  2. Wenn ein Bombenpunkt einen Widerstandspunkt bombardiert, der 2 Grenzen berührt (dh eine Ecke), bombardieren Sie bis zu diesem Punkt 0. Andernfalls bombardieren Sie jeden, bis einer der Widerstandspunkte, die den optimalen Bombenpunkt berühren, 0 ist.
  3. für jede Grenze: if (sum (bound) == 0) Vorausgrenze

Wiederholen, bis TOP = BOTTOM und LEFT = RIGHT

Ich werde später versuchen, den eigentlichen Code zu schreiben


1

Sie könnten die Zustandsraumplanung verwenden. Verwenden Sie beispielsweise A * (oder eine seiner Varianten) in Verbindung mit einer Heuristik f = g + hwie dieser:

  • g: Anzahl der bisher abgeworfenen Bomben
  • h: Summe über alle Werte des Gitters geteilt durch 9 (was das beste Ergebnis ist, was bedeutet, dass wir eine zulässige Heuristik haben)

1

Ich habe auch 28 Züge. Ich habe zwei Tests für den besten nächsten Zug verwendet: erstens den Zug, der die minimale Summe für das Brett ergibt. Zweitens für gleiche Summen die Bewegung, die die maximale Dichte erzeugt, definiert als:

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

Das ist Haskell. "Karte lösen" zeigt die Lösung des Motors. Sie können das Spiel spielen, indem Sie "main" eingeben und dann einen Zielpunkt eingeben, "best" für eine Empfehlung oder "quit", um das Spiel zu beenden.

AUSGABE:
* Haupt> Karte lösen
[(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

Hier scheint es eine nicht zweigeteilte passende Unterstruktur zu geben. Betrachten Sie die folgende Instanz:

0010000
1000100
0000001
1000000
0000001
1000100
0010000

Die optimale Lösung für diesen Fall hat die Größe 5, da dies die Größe einer minimalen Abdeckung der Eckpunkte eines 9-Zyklus durch seine Kanten ist.

Insbesondere dieser Fall zeigt, dass die von einigen Leuten veröffentlichte lineare Programmierentspannung nicht genau ist, nicht funktioniert und all diese anderen schlechten Dinge. Ich bin mir ziemlich sicher, dass ich "die Eckpunkte meines planaren kubischen Graphen um so wenige Kanten wie möglich abdecken" auf Ihr Problem reduzieren kann, was mich bezweifeln lässt, ob eine der gierigen / Bergsteigerlösungen funktionieren wird.

Ich sehe keinen Weg, dies im schlimmsten Fall in Polynomzeit zu lösen. Möglicherweise gibt es eine sehr clevere binäre Such- und DP-Lösung, die ich nicht sehe.

EDIT : Ich sehe, dass der Wettbewerb ( http://deadline24.pl ) sprachunabhängig ist; Sie senden Ihnen eine Reihe von Eingabedateien und Sie senden ihnen Ausgaben. Sie brauchen also nichts, was in der Polynomzeit des schlimmsten Falls läuft. Insbesondere können Sie sich die Eingabe ansehen !

Es gibt eine Reihe kleiner Fälle in der Eingabe. Dann gibt es einen 10x1000-Fall, einen 100x100-Fall und einen 1000x1000-Fall. Die drei großen Fälle sind alle sehr brav. Horizontal benachbarte Einträge haben normalerweise den gleichen Wert. Auf einer relativ bulligen Maschine kann ich alle Fälle durch Brute-Forcing mit CPLEX in nur wenigen Minuten lösen. Ich hatte Glück auf der 1000x1000; Die LP-Relaxation hat zufällig eine ganzheitliche optimale Lösung. Meine Lösungen stimmen mit den .ansim Testdatenpaket enthaltenen Dateien überein .

Ich wette, Sie können die Struktur der Eingabe viel direkter verwenden als ich, wenn Sie sie sich ansehen. Es scheint, als könnten Sie die erste Reihe oder zwei oder drei wiederholt abschneiden, bis Sie nichts mehr übrig haben. (Sieht so aus, als würden im 1000x1000 alle Zeilen nicht zunehmen? Ich denke, dort kommt Ihr "Teil B" her?)


Jep. Manchmal überspringe ich einfach "irrelevante" Teile des Textes. Nur kurz auf die Idee kommen und so weiter. Dieses Mal ändert sich der Pegel im Grunde genommen von leicht zu schwer wie die Hölle: P Wie auch immer, ich weiß, Sie können versuchen, eine Heuristik mit "bekanntem" Eingabesatz zu erstellen. Andererseits denke ich nur, dass es einen Algorithmus geben muss, der während 5 Stunden problemlos funktioniert, wenn die Antwort keine Prozentpunkte ist. Alles, was ich fand, war zu komplex. Dann habe ich es genauer gelesen, als jemand nach der Herkunft gefragt hat :)
abc

Wir können uns bedanken, dass viele Leute ein nettes Problem haben, über das sie nachdenken müssen, aber bezweifeln, dass es in polynomialer Zeit möglich ist. Es ist lustig, wie eine einfache Einschränkung das Aufgabenniveau von einfach auf unmöglich ändert.
abc

@Kostek: Entschuldigung, wenn ich unklar war. Ich bin ... ziemlich schlecht darin, Erklärungen auf einem für das Publikum angemessenen Niveau abzugeben. :) Wo war ich unklar?
tmyklebu

1

Ich kann mir keine Möglichkeit vorstellen, die tatsächliche Anzahl zu berechnen, ohne nur die Bombenkampagne mit meiner besten Heuristik zu berechnen, und hoffe, dass ich ein vernünftiges Ergebnis erhalte.

Meine Methode besteht also darin, für jede Zelle eine Bombing-Effizienz-Metrik zu berechnen, die Zelle mit dem höchsten Wert zu bombardieren und den Prozess zu wiederholen, bis ich alles abgeflacht habe. Einige haben befürwortet, einfachen potenziellen Schaden (dh Punktzahl von 0 bis 9) als Metrik zu verwenden, aber dies wird nicht erreicht, indem hochwertige Zellen geschlagen werden und keine Schadensüberlappung verwendet wird. Ich würde berechnen cell value - sum of all neighbouring cells, jedes positive auf 0 zurücksetzen und den absoluten Wert von allem negativen verwenden. Intuitiv sollte diese Metrik eine Auswahl treffen, die dazu beiträgt, die Schadensüberlappung bei Zellen mit hoher Anzahl zu maximieren, anstatt diese direkt zu zerstören.

Der folgende Code erreicht die vollständige Zerstörung des Testfelds in 28 Bomben (beachten Sie, dass die Verwendung von potenziellem Schaden als Metrik 31 ergibt!).

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();
    }           
  }
}

Das resultierende Bombenmuster wird wie folgt ausgegeben (Feldwerte links, Metrik rechts)

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

Dies kann mit einem Baum der Tiefe O (3 ^ (n)) gelöst werden. Wobei n die Summe aller Quadrate ist.

Betrachten Sie zunächst, dass es trivial ist, das Problem mit einem Baum von O (9 ^ n) zu lösen, und betrachten Sie einfach alle möglichen Bombardierungsorte. Ein Beispiel finden Sie in der Implementierung von Alfe .

Als nächstes stellen wir fest, dass wir daran arbeiten können, von unten nach oben zu bombardieren und trotzdem ein minimales Bombenmuster zu erhalten.

  1. Beginnen Sie in der unteren linken Ecke.
  2. Bombardiere es in Vergessenheit mit den einzigen Spielen, die Sinn machen (oben und rechts).
  3. Bewegen Sie ein Quadrat nach rechts.
  4. Während das Ziel einen Wert größer als Null hat, betrachten Sie jedes der beiden sinnvollen Spiele (gerade nach oben oder oben und rechts), reduzieren Sie den Wert des Ziels um eins und erstellen Sie für jede Möglichkeit einen neuen Zweig.
  5. Bewegen Sie einen anderen nach rechts.
  6. Während das Ziel einen Wert größer als Null hat, betrachten Sie jedes der 3 sinnvollen Spiele (oben links, oben und oben rechts), reduzieren Sie den Wert des Ziels um eins und erstellen Sie für jede Möglichkeit einen neuen Zweig.
  7. Wiederholen Sie die Schritte 5 und 6, bis die Zeile entfernt ist.
  8. Bewegen Sie sich eine Reihe nach oben und wiederholen Sie die Schritte 1 bis 7, bis das Rätsel gelöst ist.

Dieser Algorithmus ist richtig, weil

  1. Es ist notwendig, jede Zeile irgendwann zu vervollständigen.
  2. Das Abschließen einer Reihe erfordert immer ein Spiel entweder über, unter oder innerhalb dieser Reihe.
  3. Es ist immer genauso gut oder besser, ein Spiel über der niedrigsten ungeklärten Reihe zu wählen als ein Spiel in der Reihe oder unter der Reihe.

In der Praxis ist dieser Algorithmus regelmäßig besser als sein theoretisches Maximum, da er regelmäßig Nachbarn bombardiert und die Größe der Suche verringert. Wenn wir annehmen, dass jede Bombardierung den Wert von 4 zusätzlichen Zielen verringert, wird unser Algorithmus in O (3 ^ (n / 4)) oder ungefähr O (1,3 ^ n) ausgeführt.

Da dieser Algorithmus immer noch exponentiell ist, ist es ratsam, die Suchtiefe zu begrenzen. Wir können die Anzahl der zulässigen Verzweigungen auf eine bestimmte Anzahl X beschränken, und sobald wir so tief sind, zwingen wir den Algorithmus, den besten Pfad zu wählen, den er bisher identifiziert hat (den, der die minimale Gesamtsumme der Platine in einem seiner Terminalblätter aufweist ). Dann wird garantiert, dass unser Algorithmus in O (3 ^ X) -Zeit ausgeführt wird, aber es wird nicht garantiert, dass er die richtige Antwort erhält. Wir können jedoch immer X erhöhen und empirisch testen, ob sich der Kompromiss zwischen erhöhter Berechnung und besseren Antworten lohnt.


1

Bewertungsfunktion, Gesamtsumme:

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];
}

Zielfunktion:

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;
}

Zerstörungsfunktion:

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]--;
    }
}

Zielfunktion:

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;
}

lineare Maximierungsfunktion:

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

Dies ist nicht optimal, kann aber durch die Suche nach einer besseren Bewertungsfunktion optimiert werden.

.. aber als ich über dieses Problem nachdachte, dachte ich, dass eines der Hauptprobleme darin besteht, irgendwann verlassene Zahlen in der Mitte von Nullen zu bekommen, also würde ich einen anderen Ansatz wählen .. das ist, dominierende Minimalwerte in Null zu dominieren, und dann versuchen Escape-Nullen wie möglich, was zu einer allgemeinen Minimierung der minimalen vorhandenen Werte führt


0

Dieses Problem besteht lediglich darin, eine Bearbeitungsentfernung zu berechnen. Berechnen Sie einfach eine Variante des Levenshtein-Abstands zwischen der angegebenen Matrix und der Nullmatrix, wobei Änderungen durch Bombenangriffe ersetzt werden. Verwenden Sie dazu die dynamische Programmierung, um die Abstände zwischen Zwischenarrays zu speichern. Ich schlage vor, einen Hash der Matrizen als Schlüssel zu verwenden. In Pseudo-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

Dies war eine Antwort auf die erste gestellte Frage. Ich hatte nicht bemerkt, dass er die Parameter geändert hatte.

Erstellen Sie eine Liste aller Ziele. Weisen Sie dem Ziel einen Wert zu, der auf der Anzahl der positiven Werte basiert, die von einem Abfall betroffen sind (selbst und alle Nachbarn). Der höchste Wert wäre eine Neun.

Sortieren Sie die Ziele nach der Anzahl der betroffenen Ziele (absteigend), wobei eine sekundäre absteigende Sortierung nach der Summe jedes betroffenen Ziels erfolgt.

Wirf eine Bombe auf das Ziel mit dem höchsten Rang, berechne die Ziele neu und wiederhole sie, bis alle Zielwerte Null sind.

Einverstanden ist dies nicht immer das Optimalste. Beispielsweise,

100011
011100
011100
011100
000000
100011

Dieser Ansatz würde 5 Bomben erfordern, um zu klären. Optimalerweise könnten Sie es in 4 schaffen. Trotzdem ziemlich nah dran und es gibt kein Backtracking. In den meisten Situationen ist es optimal oder sehr nahe.

Unter Verwendung der ursprünglichen Problemnummern löst dieser Ansatz 28 Bomben.

Hinzufügen von Code zur Demonstration dieses Ansatzes (mithilfe eines Formulars mit einer Schaltfläche):

         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];
                        }

                    }
                }
            }
        }

    }

Eine Klasse, die Sie brauchen werden:

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

1
Nicht optimal. Gegenbeispiel: 09090Dieser Ansatz erfordert 18 Bomben. Es kann in 9.
Mysticial

@Mysticial Du hast die Antwort nicht gründlich gelesen. Da dieser Algorithmus auf der Anzahl der betroffenen Felder ungleich Null basiert, würde er die mittlere Null bombardieren und in 9 Tropfen ausgeführt werden. Der hohe Wert von 9 liegt daran, dass es bis zu acht Nachbarn und sich selbst gibt.
Anthony Queen

Wie wäre es dann 1010101, 0010100?
Mysticial

@Mysticial Für die erste, erste Null, dann würde die letzte Null getroffen werden. Es wären zwei Tropfen. Das liegt daran, dass jedes Mal, wenn eine Bombe abgeworfen wird, das nächstbeste Ziel neu berechnet wird. Für das letzte Beispiel wieder die mittlere Null; Ein Tropfen.
Anthony Queen

1
@AnthonyQueen: Das funktioniert nicht. Mein Gegenbeispiel finden Sie unter chat.stackoverflow.com/transcript/message/8224273#8224273 .
Nneonneo
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.