Найдите XOR всех чисел в заданном диапазоне

99

Вам дан большой диапазон [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 ()» - вспомогательная функция.

rajneesh2k10
источник
Я немного отредактировал ваш вопрос. Мы не против объяснить причину появления некоторого кода, но нам не нужен новый список других способов решения этой проблемы. Оставьте это TopCoder.
Кев
@Kev Без проблем! Я написал это, потому что некоторые люди любят высказывать свое мнение, а не объяснять уже написанное. И любая новая идея никогда не бывает пустой тратой ...;)
rajneesh2k10
Это имеет неопределенное поведение для a<=0или для b<0. long longявляется типом со знаком, поэтому x%4отрицательный (или 0) для отрицательных входов . Возможно, вы захотите unsigned long longи / или a & 3проиндексировать массив?
Питер Кордес

Ответы:

152

Это довольно умное решение - оно использует тот факт, что в запущенных операциях XOR есть шаблон результатов. f()Функция вычисляет исключающее общий пробег из [0, а]. Взгляните на эту таблицу для 4-битных чисел:

0000 <- 0  [a]
0001 <- 1  [1]
0010 <- 3  [a+1]
0011 <- 0  [0]
0100 <- 4  [a]
0101 <- 1  [1]
0110 <- 7  [a+1]
0111 <- 0  [0]
1000 <- 8  [a]
1001 <- 1  [1]
1010 <- 11 [a+1]
1011 <- 0  [0]
1100 <- 12 [a]
1101 <- 1  [1]
1110 <- 15 [a+1]
1111 <- 0  [0]

Где первый столбец - это двоичное представление, а затем десятичный результат и его отношение к его индексу (a) в списке XOR. Это происходит потому, что все старшие биты отменяются, а два младших бита циклически повторяются каждые 4. Итак, вот как добраться до этой маленькой таблицы поиска.

Теперь рассмотрим общий диапазон [a, b]. Мы можем использовать, f()чтобы найти XOR для [0, a-1] и [0, b]. Поскольку любое значение XOR с самим собой равно нулю, f(a-1)просто отменяет все значения в запуске XOR меньше, чем a, оставляя вам XOR диапазона [a, b].

Фатальная ошибка
источник
минимальный порог диапазона равен 1, а не 0
Пенчо Ильчев
2
@PenchoIlchev Независимо от того, включает ли он 0, это вопрос спорный - (n ^ 0) == n
FatalError
2
@ rajneesh2k10 Что ж, в сериях по 4 (начиная с числа, кратного 4), все биты, кроме самого младшего, одинаковы, поэтому они чередуются между отменой друг друга или сохранением исходного значения. Верно, что младший бит циклически повторяется каждые 2, но 0 ^ 1 == 1 (т.е. они не отменяются). Причина, по которой два нижних значения являются особенными, заключается в том, что (00 ^ 01 ^ 10 ^ 11) == 00. Другими словами, каждые 4 значения, которые вы циклически перебираете, возвращают вас к 0, и поэтому вы можете отменить все такие циклы, что является почему% 4 имеет значение.
FatalError
3
@Pandrei aесть 2, а не 0.
Harold
1
Этот столбец является текущим xor, а 1 xor 2 равно 3, поэтому текущее значение в этой строке мне кажется правильным.
FatalError
58

Добавив к отличному ответу FatalError, эту строку return f(b)^f(a-1);можно было бы лучше объяснить. Короче говоря, это потому, что XOR обладает этими замечательными свойствами:

  • Это ассоциативно - размещайте скобки, где хотите
  • Он коммутативен - это означает, что вы можете перемещать операторов (они могут «коммутировать»)

Вот оба в действии:

(a ^ b ^ c) ^ (d ^ e ^ f) = (f ^ e) ^ (d ^ a ^ b) ^ c
  • Он переворачивается

Как это:

a ^ b = c
c ^ a = b

Сложение и умножение - это два примера других ассоциативных / коммутативных операторов, но они не меняют свое значение. Итак, почему эти свойства важны? Что ж, простой способ - расширить его до того, чем он является на самом деле, и тогда вы сможете увидеть эти свойства в действии.

Во-первых, давайте определим, что мы хотим, и назовем это n:

n      = (a ^ a+1 ^ a+2 .. ^ b)

Если это поможет, подумайте о XOR (^), как о добавлении.

Также определим функцию:

f(b)   = 0 ^ 1 ^ 2 ^ 3 ^ 4 .. ^ b

bбольше чем a, поэтому, просто поставив несколько дополнительных скобок (что мы можем сделать, потому что это ассоциативно), мы также можем сказать следующее:

f(b)   = ( 0 ^ 1 ^ 2 ^ 3 ^ 4 .. ^ (a-1) ) ^ (a ^ a+1 ^ a+2 .. ^ b)

Что упрощает:

f(b)   = f(a-1) ^ (a ^ a+1 ^ a+2 .. ^ b)

f(b)   = f(a-1) ^ n

Затем мы используем это свойство разворота и коммуникативность, чтобы дать нам волшебную строку:

n      = f(b) ^ f(a-1)

Если бы вы думали о XOR как о сложении, вы бы добавили здесь вычитание. XOR - это XOR, а добавление - вычитание!

Как я сам это придумал?

Помните о свойствах логических операторов. Работайте с ними почти как сложение или умножение, если это помогает. Кажется необычным, что и (&), xor (^) и или (|) ассоциативны, но это так!

Сначала запустите наивную реализацию, поищите шаблоны в выходных данных, а затем начните находить правила, подтверждающие, что шаблон верен. Еще больше упростите реализацию и повторите. Вероятно, это путь, по которому пошел первоначальный создатель, подчеркнутый тем фактом, что он не полностью оптимален (т.е. использовать оператор switch, а не массив).

Люк Бриггс
источник
3
Это напоминает мне мой курс дискретной математики, который я прошел в прошлом году в университете. Веселые дни. Что сразу пришло мне в голову после прочтения этого комикса XKCD .
Шон Фрэнсис Н. Балле
3

Я обнаружил, что приведенный ниже код также работает как решение, указанное в вопросе.

Может быть, это немного оптимизировано, но это именно то, что я получил, наблюдая за повторением, как указано в принятом ответе,

Я хотел бы знать / понимать математическое доказательство данного кода, как объяснено в ответе @Luke Briggs

Вот этот код JAVA

public int findXORofRange(int m, int n) {
    int[] patternTracker;

    if(m % 2 == 0)
        patternTracker = new int[] {n, 1, n^1, 0};
    else
        patternTracker = new int[] {m, m^n, m-1, (m-1)^n};

    return patternTracker[(n-m) % 4];
}
Парт Вишваджит
источник
2

Решил проблему с рекурсией. Я просто делю набор данных на почти равные части для каждой итерации.

public int recursion(int M, int N) {
    if (N - M == 1) {
        return M ^ N;
    } else {
        int pivot = this.calculatePivot(M, N);
        if (pivot + 1 == N) {
            return this.recursion(M, pivot) ^ N;
        } else {
            return this.recursion(M, pivot) ^ this.recursion(pivot + 1, N);
        }
    }
}
public int calculatePivot(int M, int N) {
    return (M + N) / 2;
}

Сообщите мне свои мысли по поводу решения. Рад получать отзывы об улучшениях. Предлагаемое решение вычисляет XOR в сложности 0 (log N).

Спасибо

Абхиджит Сонаван
источник
У этого есть такая же вычислительная сложность с обычным вычислением m ^ (m + 1) ^ ... ^ (n-1) ^ n. Это 0 (n).
Thế Anh Nguyễn
0

Для поддержки XOR от 0 до N приведенный код необходимо изменить, как показано ниже:

int f(int a) {
    int []res = {a, 1, a+1, 0};
    return res[a % 4];
}

int getXor(int a, int b) {
    return f(b) ^ f(a);
}
Мохаммад Назмул Хоссейн
источник