Вам дан большой диапазон [a, b], где «a» и «b» обычно могут быть от 1 до 4 000 000 000 включительно. Вы должны узнать XOR всех чисел в заданном диапазоне.
Эта проблема использовалась в TopCoder SRM. Я видел одно из решений, представленных в матче, и не могу понять, как оно работает.
Может ли кто-нибудь помочь объяснить выигрышное решение:
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);
}
Вот getXor()
фактическая функция для вычисления xor всех чисел в переданном диапазоне [a, b], а «f ()» - вспомогательная функция.
a<=0
или дляb<0
.long long
является типом со знаком, поэтомуx%4
отрицательный (или 0) для отрицательных входов . Возможно, вы захотитеunsigned long long
и / илиa & 3
проиндексировать массив?Ответы:
Это довольно умное решение - оно использует тот факт, что в запущенных операциях XOR есть шаблон результатов.
f()
Функция вычисляет исключающее общий пробег из [0, а]. Взгляните на эту таблицу для 4-битных чисел:Где первый столбец - это двоичное представление, а затем десятичный результат и его отношение к его индексу (a) в списке XOR. Это происходит потому, что все старшие биты отменяются, а два младших бита циклически повторяются каждые 4. Итак, вот как добраться до этой маленькой таблицы поиска.
Теперь рассмотрим общий диапазон [a, b]. Мы можем использовать,
f()
чтобы найти XOR для [0, a-1] и [0, b]. Поскольку любое значение XOR с самим собой равно нулю,f(a-1)
просто отменяет все значения в запуске XOR меньше, чемa
, оставляя вам XOR диапазона [a, b].источник
a
есть 2, а не 0.Добавив к отличному ответу FatalError, эту строку
return f(b)^f(a-1);
можно было бы лучше объяснить. Короче говоря, это потому, что XOR обладает этими замечательными свойствами:Вот оба в действии:
Как это:
Сложение и умножение - это два примера других ассоциативных / коммутативных операторов, но они не меняют свое значение. Итак, почему эти свойства важны? Что ж, простой способ - расширить его до того, чем он является на самом деле, и тогда вы сможете увидеть эти свойства в действии.
Во-первых, давайте определим, что мы хотим, и назовем это n:
Если это поможет, подумайте о XOR (^), как о добавлении.
Также определим функцию:
b
больше чемa
, поэтому, просто поставив несколько дополнительных скобок (что мы можем сделать, потому что это ассоциативно), мы также можем сказать следующее:Что упрощает:
Затем мы используем это свойство разворота и коммуникативность, чтобы дать нам волшебную строку:
Если бы вы думали о XOR как о сложении, вы бы добавили здесь вычитание. XOR - это XOR, а добавление - вычитание!
Как я сам это придумал?
Помните о свойствах логических операторов. Работайте с ними почти как сложение или умножение, если это помогает. Кажется необычным, что и (&), xor (^) и или (|) ассоциативны, но это так!
Сначала запустите наивную реализацию, поищите шаблоны в выходных данных, а затем начните находить правила, подтверждающие, что шаблон верен. Еще больше упростите реализацию и повторите. Вероятно, это путь, по которому пошел первоначальный создатель, подчеркнутый тем фактом, что он не полностью оптимален (т.е. использовать оператор switch, а не массив).
источник
Я обнаружил, что приведенный ниже код также работает как решение, указанное в вопросе.
Может быть, это немного оптимизировано, но это именно то, что я получил, наблюдая за повторением, как указано в принятом ответе,
Я хотел бы знать / понимать математическое доказательство данного кода, как объяснено в ответе @Luke Briggs
Вот этот код JAVA
источник
Решил проблему с рекурсией. Я просто делю набор данных на почти равные части для каждой итерации.
Сообщите мне свои мысли по поводу решения. Рад получать отзывы об улучшениях. Предлагаемое решение вычисляет XOR в сложности 0 (log N).
Спасибо
источник
Для поддержки XOR от 0 до N приведенный код необходимо изменить, как показано ниже:
источник