Przeskocz do treści

Delta mi!

Informatyczny kącik olimpijski

Melodia

Jakub Radoszewski

o artykule ...

  • Publikacja w Delcie: listopad 2012
  • Publikacja elektroniczna: 01-11-2012

Tym razem omówimy zadanie z pogranicza informatyki i muzykologii, które pojawiło się na Bałtyckiej Olimpiadzie Informatycznej w bieżącym roku.

W zadaniu mamy do czynienia z pewnym instrumentem muzycznym, za pomocą którego można zagrać math rodzajów nut. Muzyk ma zadaną melodię, którą chciałby zagrać – jest to sekwencja math  kolejnych nut. Niestety, nie jest to takie proste. Otóż zagranie każdej nuty wymaga zakrycia niektórych otworów instrumentu, a jeśli dwie nuty istotnie różnią się wymaganym sposobem zakrycia, to muzyk nie jest w stanie zagrać jednej z tych nut bezpośrednio po drugiej... Innymi słowy, dla każdej pary nut wiadomo z góry, czy można zagrać je pod rząd, czy też nie. Z tego względu zagranie zadanej melodii może nie być możliwe. Muzyk chciałby więc zagrać melodię możliwie najbliższą zadanej, to znaczy zagrać taką melodię złożoną z  math  nut, która będzie różniła się od zadanej na najmniejszej możliwej liczbie pozycji – i my mamy mu w tym pomóc. Dodajmy, że w naszym zadaniu liczba możliwych nut jest stosunkowo niewielka (rzędu 100), natomiast cała melodia może składać się nawet ze math nut.

Jednym z pierwszych pomysłów, jaki narzuca się po przeczytaniu tego zadania, jest programowanie dynamiczne. Ponumerujmy poszczególne rodzaje nut od math do math i oznaczmy kolejne nuty w zadanej melodii przez math  Niech dalej tablica wartości logicznych math reprezentuje podane pary nut, które można zagrać bezpośrednio jedna po drugiej; zakładamy, że dla każdego math zachodzi math Chcemy wypełnić dwuwymiarową tablicę math w której pole math oznacza najmniejszą liczbę błędów, jakie może popełnić muzyk, grając – zamiast fragmentu melodii math – jakąś melodię długości math kończącą się nutą typu math Pomijając warunki brzegowe, pola tablicy math możemy wyznaczać za pomocą prostej zależności rekurencyjnej, w której sprawdzamy wszystkie możliwe rodzaje nut, jakie mogły wystąpić w melodii bezpośrednio przed zadaną nutą:

display-math

W powyższym wzorze math oznacza 1, jeśli math a 0 w przeciwnym przypadku. To rozwiązanie działa w czasie math  i pamięci math

Da się jednak lepiej. Naturalne wydaje się skorzystanie z faktu, że powyższe rozwiązanie oblicza jedynie math  różnych wartości. Gdybyśmy tylko byli w stanie jakoś sprytniej wyznaczać minimum we wzorze math np. w czasie stałym, to uzyskalibyśmy rozwiązanie działające zadowalająco szybko. Pewną wskazówką jest tu pierwszy składnik występujący we wzorze (*). Otóż, w przeciwieństwie do drugiego składnika, może on przyjmować tylko dwie różne wartości, które zależą jedynie od tego, czy w  math -tym kroku decydujemy się zagrać poprawną nutę math czy też nie.

Przyjrzyjmy się pierwszemu z tych przypadków i spróbujmy zmienić nieco nasze podejście. Gdyby udało nam się wyznaczyć jednowymiarową tablicę math w której math to maksymalna liczba poprawnie zagranych nut podczas wykonywania początkowego fragmentu melodii długości math przy założeniu, że math -tą nutę zagramy poprawnie, to moglibyśmy już łatwo podać wynik – byłoby to maksimum ze wszystkich elementów tablicy math (dlaczego?). Przy wypełnianiu tego typu tablic za pomocą programowania dynamicznego math oblicza się zazwyczaj jako maksimum z tych wartości math dla math które „pasują do faktu math ”. W naszym przypadku przy obliczaniu math bierzemy pod uwagę te wartości math dla których istnieje poprawna (czyli możliwa do zagrania) melodia długości math zawierająca na odpowiednich pozycjach nuty math i  math

Nie dosyć, że powyższe rozwiązanie ma na starcie złożoność czasową math to jeszcze nie bardzo widać, jak takie math  znajdować. Jest do tego potrzebny pewien pomysł: otóż tablicę math możemy potraktować jako macierz sąsiedztwa pewnego grafu. Wówczas wartość math pasuje do math jeśli w tym grafie istnieje ścieżka długości math łącząca nuty math oraz math W tym celu wystarczy sprawdzić, czy najkrótsza ścieżka w grafie od math do math ma długość nieprzekraczającą math (to przejście myślowe wynika akurat z faktu, że math jest zawsze prawdziwe). Najkrótsze ścieżki między wszystkimi parami wierzchołków (nut) możemy wyznaczyć w czasie math  czy to za pomocą algorytmu Floyda–Warshalla, czy to wielokrotnie wykonując przeszukiwanie wszerz.

W ten sposób rozwiązaliśmy problem implementacji nowego rozwiązania, ale ciągle nie uporaliśmy się z jego paskudną złożonością czasową. Jedynym nowym pomysłem, jaki pojawił się po drodze, było wprowadzenie grafu nut. Jeśli zdecydujemy się poświęcić mu jeszcze trochę uwagi, to ta decyzja okaże się kluczowa dla skonstruowania efektywnego rozwiązania.

Potrzebne jest nam mianowicie już tylko następujące spostrzeżenie: graf nut ma zaledwie math wierzchołków, więc wszystkie najkrótsze ścieżki w tym grafie mają długości nieprzekraczające math To oznacza, że dla danego math wszystkie wartości math dla math są z pewnością dobre, a zatem przy obliczaniu math wystarczy tylko rozważyć elementy tablicy położone co najwyżej math pól wstecz oraz maksimum ze wszystkich elementów położonych jeszcze wcześniej!

Zanim jednak zaczniemy świętować nasz sukces, musimy poczynić przykre spostrzeżenie, że graf nut może nie być spójny. To oznacza, że dla danego math niektóre nawet bardzo odległe wartości math mogą nie być dobre. Przezwyciężenie tej trudności okazuje się już tylko kwestią czasu. Możemy, na przykład, pamiętać zawsze math ostatnich elementów tablicy math a także maksima ze wszystkich wcześniejszych elementów pogrupowane po poszczególnych nutach (czyli math grupujemy po math ). Sprytniejsze rozwiązanie polega na rozważeniu każdej spójnej składowej grafu nut z osobna i zastosowaniu dla niej metody z jednym pamiętanym maksimum – wówczas nuty melodii występujące poza składową traktujemy po prostu jako niemożliwe do zagrania. W obu przypadkach otrzymujemy algorytm działający w czasie math i pamięci math  Do tego musimy jeszcze doliczyć koszt wyznaczenia najkrótszych ścieżek: czas math  i pamięć math

Dodajmy na koniec, że nasz muzyk nie będzie specjalnie zadowolony, jeśli podamy mu jedynie minimalną liczbę błędów, jakie może popełnić – chciałby przecież wiedzieć, jaką melodię powinien zagrać! Polecamy zatem Czytelnikowi zastanowić się nad tym, jak odtworzyć wynik.