Dynamische Programmierung bei Olympiadenproblemen

Hallo!

Kürzlich habe ich Probleme aus dem Timus Online Judge- Archiv gelöst und bin auf einen Abschnitt namens Dynamic Programming Tasks gestoßen . Diese Art von Aufgabe ist für mich von besonderem Interesse, da dieser Ansatz häufig die Geschwindigkeit und Eleganz der Lösung gewährleistet. Was ist dynamische Programmierung?

Dynamische Programmierung ist ein Ansatz zur Lösung von Problemen, bei denen eine Unterteilung in Teilaufgaben erfolgt, die im Vergleich zur ursprünglichen „einfacher“ sind. Das Wort "dynamisch" hat eine ähnliche Bedeutung wie "induktiv": Es wird angenommen, dass die Antwort für eine bestimmte Bedeutung bekannt ist kund ich möchte die Antwort finden für k+1. In der Mathematik wird dies als induktiver Übergang bezeichnet und ist die Hauptidee der dynamischen Programmierung.

Einfache Beispiele


Die auffälligste und indikativste Aufgabe ist die Aufgabe des Rechnens nNummer der Fibonacci-Sequenz. Es ist bekannt, dass die Sequenz die folgenden Eigenschaften hat:

F0=F1=1, Fn=Fn1+Fn2.


Dies impliziert sofort die Wiederholungsformel:

int Fibonacci(int n) { if(n == 1 || n == 2) return 1; return Fibonacci(n-1) + Fibonacci(n-2); } 

Wenn die Rekursion nach einer Zahl "vom Ende" sucht, berechnet die folgende Methode nacheinander alle dazwischen liegenden Zahlen 0und n::

 int dpFibonacci(int n) { int prev1 = 1; int prev2 = 1; int curr = 0; for(int j = 2; j < n; j++) { curr = prev1 + prev2; prev1 = prev2; prev2 = curr; } return curr; } 

Es ist klar, dass für ausreichend große nDieser Algorithmus arbeitet viel schneller: Er berechnet keine Zwischenwerte mehrmals. Betrachten Sie ein etwas komplexeres Beispiel.

Beispiel 1. Sie gehen auf einer mautpflichtigen Treppe. Auftreten iSchritt müssen Sie bezahlen aiMünzen. Sie können zum nächsten Schritt übergehen oder über einen springen. Aufgabe: bestehen nSchritte und geben Sie so wenig Münzen wie möglich aus.

Es ist klar, dass wir bei jedem Schritt die Anzahl der „Zahlungen“ minimieren, aber wir können auf einen sehr teuren Schritt stoßen, den wir vermeiden möchten. Erstellen Sie ein Array von Werten din dem auf j-Der Platz ist die (minimale) Anzahl von Münzen, die ausgegeben werden müssen, um dorthin zu gelangen jth Schritt. Es ist sofort klar, dass d1=a1, d2=a2. Und dann werden wir mindestens die beiden vorherigen Schritte ausführen und die Kosten für den Schritt selbst addieren:

di= min left(di1,di2 right)+ai.



Wir ändern die Bedingungen des Problems ein wenig: Nehmen wir an, dass Sie in einigen Schritten Münzen erhalten können (dies bedeutet das ak<0) Was muss im Algorithmus geändert werden, damit das richtige Ergebnis erzielt wird?

Lösung
Es ist nur notwendig, den „Anfang“ unserer Dynamik zu ändern. Wenn die erste Treppe uns keine Münzen bringt, ist es jedoch ratsam, darüber zu springen, wenn a1<0Es ist besser, Ihre Münzen zu sammeln. Also d2= min left(0,d1 right)+a2.

Stellen Sie sich ein anderes Beispiel vor, das eine „zweidimensionale“ Dynamik verwendet.

Beispiel 2. Im Labyrinth gibt es n timesmRäume, von denen jeder Gold enthält (in einem Käfig (i,j)Lügen aijGold). Die Aufgabe besteht darin zu bestimmen, welche maximale Menge Gold mit einer optimalen Route von einem Punkt aus gesammelt werden kann (0,0)auf den Punkt (n,m)wenn Sie entweder nach unten oder rechts gehen können.

Wir wollen also den besten Weg zur Zelle kennen (i,j). Wir können aus zwei Zellen hierher kommen - (i1,j)und (i,j1). Vorausgesetzt, die optimalen Routen für diese beiden Zellen sind bekannt (sie sind in einer Tabelle gespeichert d), dann die Antwort für die Zelle (i,j)erhalten wie folgt:

dij= max left(di1j,dij1 right)+aij.


Dies ist eine weitere klassische dynamische Programmieraufgabe, deren Modifikationen bei Sportprogrammieraufgaben häufig vorkommen. Eine ähnliche Aufgabe wird hier näher erläutert.

Anspruchsvollere Aufgaben

Auf Wunsch kann ein dynamischer Ansatz beliebig angeschraubt werden. Betrachten Sie eine Aufgabe aus dem Timus Online Judge-Archiv.

Die mathematische Formulierung des Problems lautet wie folgt: Es ist erforderlich, die Mindestanzahl von Begriffen zu finden, die erforderlich sind, um eine bestimmte Anzahl in vollständige Quadrate zu zerlegen.

Nehmen wir an, wir kennen nach wie vor die Antworten für alle Zahlen k1die in einem Array gespeichert sind dund wir würden gerne finden dk.

Nimm diese Nummer kund analysieren, welche Situationen sein können:

  1. kist ein volles Quadrat. In diesem Fall dk=1.
  2. Vielleicht die vorherige Nummer k1war ein komplettes Quadrat. Dann dk=dk1+1.

Im Allgemeinen scheint die Option, eine Einheit zur vorherigen hinzuzufügen, nicht so schlecht zu sein.

Wir gehen wie folgt vor: Wir suchen eine Zersetzung k=q2+sso dass

dq2+ds<dk1+1.

Als q2- dann volles Quadrat dq2=1und

ds<dk1,

Das heißt, wir haben eine Partition gefunden, die einfach besser ist als dk1+1und die Antwort in diesem Fall wird sein

dk=ds+dq2=ds+1.



Beispiel für Java-Code, der diesen Algorithmus implementiert:
 for(int k = 1; k <= n; k++) { int best = d[k - 1] + 1; //      int q = 1; while(k - q*q >= 0) { // k = q*q + s if(k - q*q == 0) { // k -   best = 1; break; } else if(d[k - q*q] < best) best = d[k - q*q] + 1; q++; } d[k] = best; } 


Betrachten Sie das folgende Problem . Das Ziel ist es, eine Treppe aus zu bauen NWürfel nach den Regeln:

  1. Die Treppe hat mindestens zwei Stufen.
  2. Eine Treppe kann nicht zwei identische Stufen haben.
  3. Die Stufen der Treppe verlaufen in aufsteigender Reihenfolge (dh die nächste ist größer als die vorherige).

Dieses Mal werden wir eine zweidimensionale Dynamik aufbauen. Erstellen Sie eine Tabelle din der die Position (i,j)die Anzahl der Treppen bestehend aus iWürfel, deren Höhe nicht überschreitet j. Wenn es klappt, ist die Antwort auf unser Problem die Summe

 sum limitj=1ndnj.


Wir werden also das Problem lösen, die Anzahl der Treppen zu ermitteln, aus denen sich die Treppe zusammensetzt iWürfel, die groß sind j. Das Bild zeigt die Treppen, die hineinfallen d74::

Da wir alle Treppen kennen, die aus weniger Würfeln bestehen, werden wir die Treppen „abspalten“ (i,j)rechte Spalte. Das Ergebnis ist eine Treppe c ijWürfel. Beispiel für i=9, j=4::

Aber für solche Treppen ist das Ergebnis bereits bekannt, so dass wir alle diese Treppen mit einem Zyklus durch sortieren werden kund addieren Sie alle Ergebnisse. Auf diese Weise,

dij= sum limitk=1j1dijk.


Jetzt werden wir die Höhen der Treppen sortieren:

dij= sum limitk=1j1dijk, j= overline1,i.

Endlich ändern ivon 1vorher nWir bekommen die Antwort.

Wichtig : Bei der Erstellung unserer Matrix ist dies zu berücksichtigen dii=1, da sonst einige Arten von Treppen „verloren gehen“ (wenn sie „abgespalten“ werden), aber es versteht sich von selbst, dass eine solche Treppe die Bedingungen des Problems nicht erfüllt, lautet die Antwort die Nummer dnn1.

Beispiel für Java-Code, der diesen Algorithmus implementiert:

 dp = new long[n + 1][n+1]; d[1][1] = 1; d[2][1] = 0; d[2][2] = 1; for(int i = 3; i < n + 1; i++) { for(int j = 2; j <i; j++) { long cnt = 0; for(int k = 1; k < j; k++) { cnt += d[i - j][k]; } d[i][j] = cnt; } d[i][i] = 1; //    } long answer = 0L; for(int i = 0; i <= n; i++) { answer += d[n][i]; } answer--; //     


Die nächste Aufgabe wird mit einem eindimensionalen Array gelöst.

Also was wir haben. Der erste ent kennt 2 Wörter. Jeder ent lehrt alle Wörter, die er selbst kennt, zwei ents: jung und alt. Im Gegenzug wurden den Jungen so viele Wörter beigebracht, wie er bereits weiß, und den Alten wurde nur ein Wort beigebracht. Sie müssen wissen, wie viele Ents genau wissen KWörter (es ist notwendig, die Anzahl dieser ents modulo abzuleiten P)

Die Lösung ist ganz einfach. Erstellen Sie ein Array din dem auf i-th Platz speichern wir die Anzahl der ents (modulo P) wer weiß iWorte. Alles beginnt mit dem ersten Ent, der daher zwei Wörter kennt d2=1. Und dann ist alles einfach:

  • Alle Enten, die eine ungerade Anzahl von Wörtern kennen, sind alt und können nur von den vorherigen lernen. Deshalb für ungerade i: di=di1;
  • Was die Ents betrifft, die eine gerade Anzahl von Wörtern kennen, so sind dies alle diejenigen, die die gleiche Anzahl von Wörtern von Elfen (jung) erhalten haben. +diejenigen, die aus dem Vorherigen gelernt haben (alt); das heißt, für gerade iwir haben di=di backslash2+di1.

Es bleibt das Berechnungsmodulo zu behandeln. Um keine großen Zahlen zu speichern, werden wir uns sofort an alle modulo-Werte erinnern.

Beispiel für Java-Code, der diesen Algorithmus implementiert:
 int[] d = new int[K + 1]; if(K >= 2) d[2] = 1; if(P != 1) { for(int i = 3; i <= K; i++) { if(i % 2 != 0) { d[i] = d[i - 1]; } else { d[i] = ((d[i/2] % P) + d[i - 1] % P) % P; } } } else d[K] = 0; 


Verwendete Ressourcen:

  1. Timus Online Richter;
  2. Ein bisschen über dynamische Programmierung;
  3. Vergleichseigenschaften modulo.

Source: https://habr.com/ru/post/de418867/


All Articles