Verbindung von Junction Boxes (Dosen) zu Netzwerken basierend auf Distanz:
- Teil 1: Nach 1000 Verbindungen: Größe der 3 größten Netzwerke multiplizieren
- Teil 2: X-Koordinaten des letzten Paares multiplizieren, das alle Dosen zu einem Netzwerk verbindet
Jede Zeile enthält Koordinaten einer Dose (kommasepariert):
1,2,3
4,5,6
7,8,9
Koordinaten können nur exakt 3 Dimensionen haben (3D).
Kruskal's MST-Algorithmus mit Union-Find:
Union-Find (Disjoint Set Union):
def finde(eltern, i):
if eltern[i] != i:
eltern[i] = finde(eltern, eltern[i])
return eltern[i]Path Compression:
- Rekursives Finden der Wurzel
- Optimierung: Flacht den Baum ab
- Jeder besuchte Knoten zeigt direkt auf Wurzel
- Amortisierte Komplexität: Fast O(1)
def union(eltern, rang, i, j):
ri, rj = finde(eltern, i), finde(eltern, j)
if ri != rj:
if rang[ri] < rang[rj]:
eltern[ri] = rj
else:
eltern[rj] = ri
if rang[ri] == rang[rj]:
rang[ri] += 1
return True # Verbindung war erfolgreich
return False # Waren schon verbundenUnion by Rank:
- Kleinerer Baum wird unter größeren gehängt
- Verhindert degenerierte Bäume (lineare Ketten)
- Hält Bäume flach
return True/False: Zeigt an, ob Verbindung neu war
def zaehle_circuits(eltern, n):
wurzeln = set()
for i in range(n):
wurzeln.add(finde(eltern, i))
return len(wurzeln)Netzwerk-Zählung:
- Findet alle eindeutigen Wurzeln
- Anzahl Wurzeln = Anzahl separate Netzwerke
dosen = [[int(x) for x in dose.strip().split(",")] for dose in aufgabe.zeilen]
distanzen = sorted([(sum((a - b) ** 2 for a, b in zip(dose1, dose2)), idx1, idx2, dose1[0], dose2[0])
for (idx1, dose1), (idx2, dose2) in itertools.combinations(enumerate(dosen), 2)])Logik:
-
Parse Koordinaten: Jede Zeile wird zu Liste von Integers
-
Alle Paare generieren:
itertools.combinations(enumerate(dosen), 2)
enumerate(dosen):(index, koordinaten)Paarecombinations(..., 2): Alle 2er-Kombinationen- Anzahl: n * (n-1) / 2 Paare
-
Quadrierte Euklidische Distanz:
sum((a - b) ** 2 for a, b in zip(dose1, dose2))
- Funktioniert für beliebige Dimensionen
- Keine Wurzel nötig: Sortierung ist gleich
- Schneller ohne
sqrt()
-
Tuple-Struktur:
(distanz, idx1, idx2, x1, x2)
distanz: Für Sortierungidx1, idx2: Für Union-Findx1, x2: X-Koordinaten für Teil 2
-
Sortierung: Nach Distanz (kürzeste zuerst)
n = len(dosen)
eltern = list(range(n))
rang = [0] * n
for distanz, idx1, idx2, x1, x2 in distanzen[:1000]:
union(eltern, rang, idx1, idx2)Initialisierung:
eltern = [0, 1, 2, ...]: Jede Dose ist eigene Wurzelrang = [0, 0, 0, ...]: Alle Bäume haben Rang 0
Verbinde 1000 kürzeste Paare:
- Ignoriere ob schon verbunden
- Union-Find verhindert Zyklen automatisch
netzwerke = {}
for i in range(n):
wurzel = finde(eltern, i)
netzwerke.setdefault(wurzel, []).append(i)
sortiert = sorted(netzwerke.values(), key=len, reverse=True)
aufgabe.loesung1 = len(sortiert[0]) * len(sortiert[1]) * len(sortiert[2])Netzwerke gruppieren:
- Dictionary:
{wurzel: [dose1, dose2, ...]} - Alle Dosen mit gleicher Wurzel = ein Netzwerk
3 größte finden:
- Sortiere nach Größe (absteigend)
- Multipliziere Größen der ersten 3
for distanz, idx1, idx2, x1, x2 in distanzen[1000:]:
if union(eltern, rang, idx1, idx2):
if zaehle_circuits(eltern, n) == 1:
aufgabe.loesung2 = x1 * x2
breakLogik:
-
Fahre fort ab Paar 1000:
distanzen[1000:] -
Nur neue Verbindungen:
if union(...)- Überspringt Paare, die schon im gleichen Netzwerk sind
- Effizienter als immer zu zählen
-
Prüfe auf Vereinigung:
zaehle_circuits(eltern, n) == 1- Wenn nur noch 1 Wurzel existiert
- Alle Dosen sind verbunden
-
Speichere Ergebnis:
x1 * x2- X-Koordinaten des letzten verbundenen Paares
- Break sofort
Eingabe:
0,0
3,0
0,3
6,0
Dosen: [[0,0], [3,0], [0,3], [6,0]]
Distanzen (sortiert):
(9, 0, 1, 0, 3) # (0,0) - (3,0): dist = 3² = 9
(9, 0, 2, 0, 0) # (0,0) - (0,3): dist = 3² = 9
(18, 1, 2, 3, 0) # (3,0) - (0,3): dist = 3² + 3² = 18
(36, 0, 3, 0, 6) # (0,0) - (6,0): dist = 6² = 36
(45, 1, 3, 3, 6) # (3,0) - (6,0): dist = 3² = 9 (Fehler im Beispiel)
...
Nach ersten 3 Verbindungen:
- Union(0, 1): Netzwerk {0, 1}
- Union(0, 2): Netzwerk {0, 1, 2}
- Union(1, 3): Netzwerk {0, 1, 2, 3}
Alle verbunden nach 3 Verbindungen (bei nur 4 Dosen).
Distanz-Berechnung:
- Zeit: O(n² * d) wobei n = Anzahl Dosen, d = Dimensionen
- Platz: O(n²) für alle Distanzen
Sortierung:
- Zeit: O(n² log n)
Union-Find Operationen:
- Zeit: O(α(n)) pro Operation (fast konstant)
- α = Inverse Ackermann-Funktion (praktisch ≤ 4)
Teil 1:
- Zeit: O(1000 * α(n) + n) = O(n)
- 1000 Union-Operationen + n Find-Operationen
Teil 2:
- Zeit: O(k * α(n)) wobei k = Anzahl Verbindungen bis Vereinigung
- Schlechtester Fall: O(n * α(n))
Gesamt:
- Zeit: O(n² * d + n² log n)
- Platz: O(n²)
Kruskal's Algorithmus:
- Greedy MST-Algorithmus
- Fügt kürzeste Kanten hinzu, die keinen Zyklus bilden
- Union-Find ist perfekt für Zyklus-Erkennung
Quadrierte Distanz:
- Vermeidet teure
sqrt()Berechnung - Sortierung bleibt gleich
- Nur für Distanz-Vergleiche, nicht absolute Werte
Optimale Verbindungs-Anzahl:
- MST mit n Knoten hat genau n-1 Kanten
- Hier: 1000 Kanten für Teil 1, dann weiter für Teil 2
Path Compression:
- Macht wiederholte Finds sehr schnell
- Essentiell für gute Performance
Union by Rank:
- Verhindert degenerierte Bäume
- Zusammen mit Path Compression: Fast O(1)
Warum 1000?
- Puzzle-spezifische Zahl
- Teilt MST-Konstruktion in zwei Phasen
- Teil 1 schaut auf Zwischenstand, Teil 2 auf Vollendung
itertools.combinations:
- Effizient für alle Paare
- Keine Duplikate, keine Selbst-Paare
- Pythonischer als nested loops