Przeskocz do treści

Delta mi!

Złośliwy problem (MAX , +) i kubełkowe struktury danych

Wojciech Śmietanka

o artykule ...

  • Publikacja w Delcie: kwiecień 2013
  • Publikacja elektroniczna: 30-03-2013
  • Autor: Wojciech Śmietanka
    Afiliacja: student, Wydział Matematyki Informatyki i Mechaniki Uniwersytetu Warszawskiego
  • Wersja do druku [application/pdf]: (266 KB)
obrazek

Dany jest ciąg złożony z  math nieujemnych liczb całkowitych math Na ciągu math chcemy wykonywać operacje dwóch rodzajów:

  • update math – modyfikuje wartości wyrazów ciągu o indeksach z przedziału od math do math w sposób zależny od parametru math (dodatniej liczby całkowitej);
  • query math – podaje zagregowaną wartość dla podciągu o indeksach od math do math

W obydwu przypadkach zakładamy, że math

Będziemy rozważali dwie różne operacje update, które oznaczymy symbolicznie przez math  i  math  W operacji math  należy do każdego wyrazu podciągu math dodać liczbę math  Z kolei operacja math  polega na zmianie wartości wyrazu math na math– innymi słowy, jeśli wyraz miał wcześniej wartość nie mniejszą niż math  to jej nie zmienia, a jeśli jego wartość była mniejsza od math  to jego nową wartością jest math

Podobnie definiujemy dwie operacje query: math  i  math  Na zapytanie math  odpowiadamy, podając sumę wyrazów z podciągu math Odpowiedzią na zapytanie typu math  jest natomiast wartość największego wyrazu w rozważanym podciągu.

Biorąc pod uwagę wszystkie kombinacje poszczególnych operacji, otrzymujemy cztery różne warianty problemu. Każdy taki wariant oznaczamy za pomocą pary, której pierwszy wyraz opisuje rodzaj operacji modyfikacji, a drugi określa zapytanie. Mamy zatem następujące warianty: math  oraz math

Okazuje się, że nasz problem ma kilka ciekawych zastosowań. Wariant math  można wykorzystać do implementacji systemu obsługi rezerwacji miejsc w pociągu na trasie łączącej math stacji. W pociągu znajduje się ustalona liczba miejsc siedzących. Każda rezerwacja dotyczy konkretnej liczby pasażerów i wskazuje numery dwóch stacji, na których pasażerowie zamierzają wsiąść i wysiąść. Naszym celem jest przyjmować kolejno wszystkie rezerwacje, które nie powodują przepełnienia pociągu.

Oznaczmy przez math aktualną liczbę pasażerów, którzy będą w pociągu na trasie pomiędzy stacjami math oraz math Rezerwację na podróż ze stacji math do stacji math dla math  pasażerów możemy przyjąć, jeśli wartość query math  jest nie większa niż liczba miejsc w pociągu. Po przyjęciu rezerwacji wykonujemy update math

Inaczej można na to spojrzeć jak na problem kontroli przesyłania danych między serwerami połączonymi jedną linią danych o określonej przepustowości albo problem obsługi pobierania danych z Internetu przez użytkowników sieci lokalnej w zadanych przedziałach czasowych (sieć ma ograniczony transfer).

Wariant math  ma natomiast elegancką interpretację kombinatoryczną powiązaną z popularną grą Tetris. Odpowiada on mianowicie sytuacji, w której klocki, które opadają na planszę, mają regularną strukturę (np. mają kształt prostokątów), a naszym celem jest, dla każdego klocka, orzec, w którym miejscu się on zatrzyma, jeśli spuścimy go z zadanej pozycji początkowej (nie mamy możliwości wykonywania w locie obrotów ani przesunięć). Z kolei wariant math reprezentuje dynamiczny problem obliczania sum częściowych ciągu.

Trzy z podanych wariantów wyjściowego problemu mają zatem jasno określoną interpretację. Co więcej, znane są ich efektywne rozwiązania, w których koszt obsługi ciągu math  zapytań to math (szukaną strukturą danych są tzw. drzewa przedziałowe, o których więcej można przeczytać lub posłuchać na stronie http://was.zaa.mimuw.edu.pl).

Za to czwarty wariant, math  jest inny. Jego interpretacja kombinatoryczna nie jest wcale aż tak jasna, a do tego nie znamy sposobu rozwiązania go w czasie math (w tym przypadku klasyczne drzewa przedziałowe nie sprawdzają się). Okazuje się jednak, że można go w miarę prosto rozwiązać, korzystając z pewnej kubełkowej struktury danych. Nasze rozwiązanie będzie działało w czasie math

Zacznijmy od podziału wyrazów ciągu pomiędzy kubełki, umieszczając w każdym kubełku, z wyjątkiem ostatniego, math wyrazów. Jeśli ostatni kubełek jest mniejszy, możemy uzupełnić go sztucznymi wyrazami do rozmiaru math Oprócz kolejnych wyrazów ciągu kubełek będzie przechowywał pewne dane pomocnicze. Zanim opiszemy strukturę kubełka, zaproponujmy trochę inny sposób myślenia o operacji update math Zamiast mówić, że dla każdego math  takiego że math wykonujemy math  można wyobrazić sobie, że dodajemy nowe ograniczenie dolne na liczby na pozycjach od math do math Wtedy wartość liczby w kubełku będzie równa maksimum z jej początkowej wartości oraz wszystkich ograniczeń dolnych jej dotyczących. Takie ograniczenia dolne będą trzymane osobno dla każdego kubełka.

Struktura kubełka będzie następująca:

  • math – tablica kolejnych wyrazów ciągu, które znajdują się w kubełku;
  • math – posortowana tablica liczb math
  • math – sumy prefiksowe tablicy math czyli math
  • math – dolne ograniczenie na wszystkie wyrazy ciągu przechowywane w kubełku, czyli maksimum z wartości math z operacji update math  dotyczących całego przedziału kubełka; aktualna wartość math-tej liczby z kubełka to zawsze math

Każdy kubełek umożliwia wykonywanie następujących operacji pomocniczych (szczegółowy opis ich implementacji znajduje się w dalszej części artykułu):

1.
math – obliczenie sumy aktualnych wartości wszystkich wyrazów ciągu zawartych w kubełku (działa w czasie math );
2.
math – obliczenie sumy wyrazów o indeksach od math do math dla math (działa w czasie math );
3.
math – aktualizacja dolnego ograniczenia na całym przedziale do wartości co najmniej math (działa w czasie math );
4.
math – zwiększenie wyrazów o indeksach od math do math dla math do wartości co najmniej math (działa w czasie math ).

Zauważmy teraz, że dowolny zakres indeksów od math do math można rozbić na pewną liczbę pełnych kubełków (oczywiście, nie więcej niż math) oraz na co najwyżej dwa niepełne kubełki (te skrajne). Dzięki temu zapytanie o sumę liczb na przedziale od math do math można podzielić na nie więcej niż math zapytań math oraz co najwyżej dwa zapytania math Stąd każde takie zapytanie obsługujemy w czasie math  Operację modyfikacji ciągu na indeksach od math do math można także wykonać w czasie math  podobnie rozbijając cały przedział na nie więcej niż math kubełków, na których wykonujemy operację math i co najwyżej dwa brzegowe kubełki z wykonywaną operacją math

Przykład 1. W tabeli przedstawiono, jak zmienia się przykładowa kubełkowa struktura danych math w wyniku pojedynczej modyfikacji. Warto zwrócić uwagę, że w środkowym kubełku zmienia się tylko dolne ograniczenie, tj. math

obrazek

Pozostaje opisać, jak korzystając z przechowywanych danych, efektywnie wykonać cztery operacje pomocnicze oferowane przez kubełek.

display-math

Ostatnią pozycję math znajdujemy, wyszukując binarnie. Zauważmy, że w tablicy math jest dokładnie math liczb mniejszych od math oraz dokładnie math liczb nie mniejszych niż math Skoro aktualna wartość każdej liczby to math więc suma aktualnych wartości wynosi math (sumujemy math najmniejszych liczb) plus suma math największych liczb, czyli math

display-math

W powyższej funkcji sumujemy aktualne wartości wyrazów z kubełka z określonych pozycji.

Aktualizacja dolnego ograniczenia na cały kubełek jest bardzo łatwa:

display-math

Jeśli pojawia się nowe dolne ograniczenie, które nie dotyczy całego zakresu obejmowanego przez kubełek, lecz jedynie jego części, należy zaktualizować liczby tylko z tego zakresu. Niestety, po takiej operacji tablice math oraz math stają się nieaktualne, więc obliczamy je ponownie. Oto zapis stosownego algorytmu:

display-math

Zaprezentowane rozwiązanie jest dosyć szybkie, jednak nie widać powodu, dla którego problemu math  nie można by rozwiązać jeszcze szybciej. Być może któremuś z Czytelników uda się skonstruować takie rozwiązanie?

Czytelnik, który do końca lipca 2013 roku przyśle do redakcji rozwiązanie opisanego problemu o najlepszej złożoności, nie gorszej jednak niż math  zostanie nagrodzony kilogramem szwajcarskiej czekolady w przesyłce prosto z Zurychu. W przypadku wielu rozwiązań o tej samej złożoności nagrodzimy subiektywnie najładniejsze, w przypadku tego samego najładniejszego rozwiązania – pierwsze spośród otrzymanych.