Dany jest pewien zakres liczb od 0 do 2^k. Oblicz sumę licznika bitów 1 dla każdej liczby z zakresu. Czy istnieje jakaś reguła, którą można to opisać? Jeśli tak to napisz algorytm, który ją wykorzysta.
Przykładowo dla zakresu [0, 8) mamy następujące zapisy binarne:
Wynik to suma liczby bitów w każdym rozwinięciu binarnym czyli 0 + 1 + 1 + 2 + 1 + 2 + 2 + 3 = 12.
Najprostsze rozwiązanie polega na zastosowaniu pętli, która dla każdej kolejnej wartości z zakresu wyliczy ile jest w niej bitów 1, a następnie zsumuje wyliczane wartości. Oto przykładowo metoda IleBitow():
Złożoność takiego rozwiązania wynosi O(2k + 1), ponieważ dominującą operacją jest tutaj zliczanie bitów 1, a to wykonujemy maksymalnie 2k razy dla 2k liczb.
Istnieje jednak pewna zależność pomiędzy kolejnymi etapami liczenia. Warto zauważyć, że jeśli zostało wygenerowane 2k - 1 liczb to wystarczy je ponownie przepisać i dołączyć na początku wartość 1. Przykładowo mając liczby {0, 1} tablicę rozszerzamy przepisując jej aktualne elementy, a potem pownie z modyfikacją czyli {0, 1, 10, 11}. Na podstawie tego można stwierdzić, że ak = 2ak - 1 + 2k - 1. Wyrazem początkowym a0 = 0
Na podstawie podanego wzoru można napisać funkcję, która będzie udzielać odpowiedzi w czasie liniowym:
Tym razem złożoność zmalała do O(k), ponieważ wykorzystujemy poprzednie wyniki, aby szybciej obliczyć kolejne. Oczywiście wadą takiej metody jest to, że nie działa dla dowolnego zakresu jak poprzednie rozwiązanie, ale w tym przypadku takie uproszczenie jest akceptowalne.
Jednak czy można uzyskać czas O(1)? Oczywiście, ale trzeba zamienić wzór rekurencyjny na wzór na n-ty wyraz ciągu. Istnieje do tego wiele metod, które na końcu zwrócą wzór ak = 2k - 1k. Można to wyjaśnić w następujący sposób:
ak = 2ak - 1 + 2k - 1
ak = 2(2ak - 2 + 2k - 1) + 2k - 1 = 4ak - 2 + 2k - 1 + 2k - 1
ak = 8ak - 2 + 2k - 1 + 2k - 1 + 2k - 1
...
ak = 2k - 1 + ... + 2k - 1 + 2k - 1 + 2k - 1 = 2k - 1k
Jak można zauważyć przy każdym podłożeniu ak - j zostaje dodatkowa wartość 2k - 1, a pierwszy czynnik jest coraz większy. Jednak dla k-tego wyrazu ak - j = ak - k = a0 0, więc pierwszy, rekurenycjny czynnik zniknie i pozostanie k wartości 2k - 1.
Zastosowanie wzoru upraszcza wzór do absolutnego minimum i osiągamy w ten sposób najlepszą możliwą złożoność.
Test funkcji można wykonać przy pomocy poniższej funkcji, która dla podanej wartości k zwróci liczbę bitów ustawiony na wartość 1.