Dostajesz duży zakres [a, b], gdzie „a” i „b” mogą zwykle wynosić od 1 do 4 000 000 000 włącznie. Musisz znaleźć XOR wszystkich liczb w podanym zakresie.
Ten problem był używany w TopCoder SRM. Widziałem jedno z zgłoszonych rozwiązań w meczu i nie jestem w stanie dowiedzieć się, jak działa.
Czy ktoś mógłby pomóc wyjaśnić zwycięskie rozwiązanie:
long long f(long long a) {
long long res[] = {a,1,a+1,0};
return res[a%4];
}
long long getXor(long long a, long long b) {
return f(b)^f(a-1);
}
Tutaj getXor()
jest rzeczywistą funkcją obliczającą xor wszystkich liczb w przekazanym zakresie [a, b] i "f ()" jest funkcją pomocniczą.
a<=0
lub dlab<0
.long long
jest typem ze znakiem, więcx%4
jest ujemny (lub 0) dla ujemnych danych wejściowych . Być może chceszunsigned long long
i / luba & 3
zindeksować tablicę?Odpowiedzi:
Jest to całkiem sprytne rozwiązanie - wykorzystuje fakt, że w działających XORach istnieje wzór wyników.
f()
Oblicza całkowitą XOR uruchamiane z [0, A]. Spójrz na tę tabelę dla liczb 4-bitowych:Gdzie pierwsza kolumna jest reprezentacją binarną, a następnie wynik dziesiętny i jego związek z indeksem (a) do listy XOR. Dzieje się tak, ponieważ wszystkie górne bity anulują się, a najniższe dwa bity cyklicznie co 4. Tak więc można dojść do tej małej tablicy przeglądowej.
Rozważmy teraz ogólny zakres [a, b]. Możemy użyć
f()
do znalezienia XOR dla [0, a-1] i [0, b]. Ponieważ każda wartość XOR ze sobą równa się zero, pof(a-1)
prostu anuluje wszystkie wartości w XOR, które są mniejsze niża
, pozostawiając XOR zakresu [a, b].źródło
a
jest 2, a nie 0.Dodając do świetnej odpowiedzi FatalError, kwestię tę
return f(b)^f(a-1);
można wyjaśnić lepiej. Krótko mówiąc, to dlatego, że XOR ma te wspaniałe właściwości:Oto oba w akcji:
Lubię to:
Dodawanie i mnożenie to dwa przykłady innych operatorów asocjacyjnych / przemiennych, ale one same się nie odwracają. Ok, więc dlaczego te właściwości są ważne? Cóż, prostą drogą jest rozszerzenie tego na to, czym naprawdę jest, a następnie możesz zobaczyć te właściwości w działaniu.
Najpierw zdefiniujmy, czego chcemy i nazwijmy to:
Jeśli to pomoże, pomyśl o XOR (^) tak, jakby to był dodatek.
Zdefiniujmy też funkcję:
b
jest większe niża
, więc po prostu bezpiecznie upuszczając kilka dodatkowych nawiasów (co możemy, ponieważ jest asocjacyjne), możemy również powiedzieć to:Co upraszcza:
Następnie używamy tej właściwości odwrócenia i przemienności, aby uzyskać magiczną linię:
Jeśli myślałeś o XOR jak o dodaniu, zostawiłbyś tam odejmowanie. XOR jest dla XOR tym, czym dodawanie jest odejmowaniem!
Jak mam to wymyślić?
Zapamiętaj właściwości operatorów logicznych. Pracuj z nimi prawie jak dodawanie lub mnożenie, jeśli to pomaga. Wydaje się niezwykłe, że i (&), xor (^) i lub (|) są skojarzone, ale tak jest!
Najpierw uruchom naiwną implementację, poszukaj wzorców w danych wyjściowych, a następnie zacznij znajdować reguły, które potwierdzają, że wzorzec jest prawdziwy. Jeszcze bardziej uprość wdrażanie i powtórz. Prawdopodobnie jest to droga, którą obrał pierwotny twórca, podkreślona faktem, że nie jest ona całkowicie optymalna (tj. Użyj instrukcji switch zamiast tablicy).
źródło
Dowiedziałem się, że poniższy kod również działa jak rozwiązanie podane w pytaniu.
Być może jest to trochę zoptymalizowane, ale właśnie to otrzymałem obserwując powtórzenia podane w zaakceptowanej odpowiedzi,
Chciałbym poznać / zrozumieć dowód matematyczny za podanym kodem, jak wyjaśniono w odpowiedzi @Luke Briggs
Oto kod JAVA
źródło
Rozwiązałem problem z rekurencją. Po prostu dzielę zbiór danych na prawie równą część dla każdej iteracji.
Daj mi znać, co myślisz o rozwiązaniu. Chętnie otrzymuję informacje zwrotne dotyczące ulepszeń. Zaproponowane rozwiązanie oblicza XOR ze złożonością 0 (log N).
Dziękuję Ci
źródło
Aby obsługiwać XOR od 0 do N, podany kod musiał zostać zmodyfikowany jak poniżej,
źródło