Dane jest równanie a = b AND c oraz dwie wartości całkowite a i b. Zakładając, że c jest mniejsze niż 2b to ile jest możliwych rozwiązań? Napisz program, który wypisze je wszystkie w dowolnej kolejności. Jeśli nie jest możliwe wyznaczenie c to program powinien wypisać odpowiedni komunikat.
Przykładowo mamy dane a = 4 = 1002 oraz b = 5 = 1012. Wiadomo, że c to liczba należąca do przedziału [0, 10), ale tylko dwie spełniają równanie. Są to 4 oraz 6. Wystarczy sprawdzić, że 100 = 101 AND 100 oraz 100 = 101 AND 110.
Rozwiązanie siłowe polega na napisaniu funkcji, która w pętli sprawdzi wszystkie możliwe rozwiązania. Złożoność takiego algorytmu jest liniowa O(n). Warto jednak zauważyć, że rozwiązania stanowią niewielki procent wszystkich możliwych. Oto przykładowy kod funkcji:
Na początku deklarowana jest pusta lista do zapisu danych, a następnie definiowany jest odpowiedni zakres w pętli i jeśli aktualny indeks po wykonaniu operacji binarnej da odpowiedni wynik to wartość indeksu zostaje dopisana na listę. Na koniec zwracana jest lista wynikowa.
Złożoność algorytmu można poprawić wyznaczając miejsca, gdzie trzeba postawić konkretną wartość, a kiedy dowolną. Oto możliwe pary (a, b) oraz akcja jaką należy podjąć:
a | b | c |
---|---|---|
0 | 0 | dowolna wartość, 0 lub 1 |
0 | 1 | tylko 0 |
1 | 0 | niemożliwe, cokolwiek i 0 daje zawsze 0 |
1 | 1 | tylko 1 |
Jak można zauważyć jeśli w parze (a, b) wystąpi para wartości (1, 0) to zadanie jest niemożliwe do rozwiązania. Jedynym przypadkiem, gdy można postawić dowolną wartość jest (0, 0), ponieważ cokolwiek i 0 zawsze da 0.
Algorytm można usprawnić następująco: na początku znajdujemy wszystkie bity o dowolnej wartości i zapisujemy ich pozycje na liście [p1, p2, .., pk]. Podczas przeglądania wartości możemy zakończyć działanie funkcji jeśli znajdziemy parę (1, 0). Na podstawie listy określamy, że jest 2k możliwych rozwiązań i je wyznaczamy. n-te rozwiązanie polega na zastąpieniu bitu pi poprzez i-ty bit n.
Dane jest a = 1002 oraz b = 1012. Wiadomo, że dowolny może być bit środkowy, a więc o indeksie 1. Istnieję tylko dwa możliwe rozwiązania. Podstawą do generowanie kolejnych rozwiązań jest 1x0. Najpierw x to 0, a potem 1. W ten sposób zostają uzyskane obydwa możliwe rozwiązania.
Poniższy kod implementuje powyższe wyjaśnienie algorytmu i zwraca listę wszystkich możliwych rozwiązań:
Dla k wierszy inicjujemy licznik na zero oraz kopiujemy aktualny numer wiersza. Nastepnie w petli przechodzimy po kolejnych bitach tak dlugo, az wyzerujemy wartosc. Za kazdym razem dodajemy najstarszy bit liczby. Na koniec obliczamy 2ile i wypisujemy wynik.
Pierwsza część polega na wyznaczeniu maski rozwiązań, która będzie potem wykorzystana do generowania rozwiązań (zmieniane będę później tylko odpowiednie bity). W zależności od wartości i-tego bitu modyfikujemy maskę. Następnie przechodzimy do generowania rozwiązań odpowiednio modyfikując bity maski i zapisując dane do wyniku. Złożoność algorytmu to O(nk), gdzie n - ilość rozwiązań, a k - ilość bitów dowolnych. Zaletą takiego rozwiązania jest to, że w każdej iteracji znajduje rozwiązanie i potrafi wyjść z funkcji poprzez wykrycie niemożliwego stanu.
W kodzie została wykorzystana metoda UstawBit(), która w podanej zmiennej ustawia i-ty bit na wartosc.
W celu przetestowania kodu można skorzystać z poniższego fragmentu programau: