Знайдіть XOR усіх чисел у заданому діапазоні


99

Вам надається великий діапазон [a, b], де 'a' і 'b' можуть бути зазвичай від 1 до 4 000 000 000 включно. Ви повинні дізнатися ХОР усіх чисел у заданому діапазоні.

Ця проблема була використана в 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 ()" є допоміжною функцією.


Я трохи відредагував ваше запитання. Ми не проти пояснювати чомусь якийсь код, але нам не потрібен новий список інших способів вирішення цього питання. Залиште це в 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]

Де перший стовпець - це двійкове подання, а потім десятковий результат та його відношення до його індексу (а) у списку XOR. Це трапляється тому, що всі верхні біти скасовуються, а найнижчі два біти проходять цикл кожні 4. Отже, ось як прийти до цієї маленької таблиці пошуку.

Тепер розглянемо загальний діапазон [a, b]. Ми можемо використовувати f()для пошуку XOR для [0, a-1] і [0, b]. Оскільки будь-яке значення XOR'd з самим собою дорівнює нулю, 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 (^) і або (|) асоціативні, але вони є!

Запустіть наївну реалізацію спочатку, знайдіть шаблони у висновку, а потім починайте знаходити правила, які підтверджують, що шаблон є правдивим. Ще більше спростіть свою реалізацію та повторіть. Це, мабуть, маршрут, який пройшов оригінальний творець, підкреслений тим, що це не зовсім оптимально (тобто використовувати оператор перемикання, а не масив).


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).
Thh 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);
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.