Извлечение самых правых N бит целого числа

В предыдущем отборочном раунде Code Jam Qualification http://code.google.com/codejam/contest/dashboard?c=433101#s=a&a=0 возникла проблема, называемая Snapper Chain. Из анализа конкурсов я узнал, что проблема требует бит-трюка, например, извлечение самых правых N бит целого числа и проверка, все ли они 1. Я видел код участника (Eireksten), который выполнял указанную операцию, как показано ниже:

(((K&(1<<N)-1))==(1<<N)-1)

Я не мог понять, как это работает. Каково использование -1 в сравнении?. Если кто-то может это объяснить, это было бы очень полезно для нас, новичков. Кроме того, любые советы по выявлению таких проблем будут высоко оценены. Я использовал наивный алгоритм для решения этой проблемы и решил решить только меньший набор данных. (Для сбора большего набора данных, который должен быть отправлен в течение 8 минут, потребовалось время.). Спасибо заранее.

Ответы

Ответ 1

В качестве примера можно использовать N = 3. В двоичном формате 1<<3 == 0b1000. Итак 1<<3 - 1 == 0b111.

В общем случае 1<<N - 1 создает число с N в двоичной форме.

Пусть R = 1<<N-1. Тогда выражение будет (K&R) == R. K&R будет извлекать последние N бит, например:

     101001010
  &        111
  ———————————— 
     000000010

(Напомним, что бит-И вернет 1 в цифре, если и только если оба операнда имеют 1 в этой цифре.)

Равенство выполняется тогда и только тогда, когда последние N бит равны 1. Таким образом, выражение проверяет, заканчивается ли K с N.

Ответ 2

Например: N = 3, K = 101010

1. (1<<N) = 001000 (shift function)
2. (1<<N)-1 = 000111 (http://en.wikipedia.org/wiki/Two's_complement)
3. K&(1<<N)-1 = 0000010 (Bitmask)
4. K&(1<<N)-1 == (1<<N)-1) = (0000010 == 000111) = FALSE

Ответ 3

Я работал над проблемой Snapper Chain и пришел сюда, чтобы найти объяснение того, как алгоритм разбивки бит, с которым я столкнулся, работал. Я нашел хорошую информацию, но мне все равно понадобилось время, чтобы разобраться в этом, будучи побитым нобом.

Здесь моя попытка объяснить алгоритм и как его найти. Если мы перечисляем все возможные мощности и состояния ВКЛ/ВЫКЛ для каждой привязки в цепочке, мы видим шаблон. Учитывая тестовый случай N = 3, K = 7 (3 привязки, 7 снимков), мы показываем состояния питания и ВКЛ/ВЫКЛ для каждой оснастки для каждой k-й привязки:

            1    2    3
  0b:1 1   1.1  1.0  0.0 -> ON for n=1
 0b:10 2   1.0  0.1  0.0
 0b:11 3   1.1  1.1  1.0 -> ON for n=1, n=2
0b:100 4   1.0  0.0  1.0
0b:101 5   1.1  1.0  1.0 -> ON for n=1
0b:110 6   1.0  0.1  0.1
0b:111 7   1.1  1.1  1.1 -> ON for n=2, n=3

Лампочка включена, когда все окунители включены и получают питание, или когда у нас есть k-я привязка, в результате чего n 1s. Еще проще, лампа включена, когда все окунители включены, так как все они должны получать питание, чтобы быть включенным (и, следовательно, лампочкой). Это означает, что для каждого k привязок требуется n 1s.

Далее, вы можете заметить, что k является бинарным числом не только для k = 7, которое удовлетворяет n = 3, но при k = 3, которое удовлетворяет n = 2 и k = 1, которое удовлетворяет n = 1. Далее, для n = 1 или 2 мы видим, что каждое число привязок, которое включает лампу, последние n цифр k всегда 1. Мы можем попытаться обобщить, что все ks, которые удовлетворяют n snappers, будут двоичным числом, заканчивающимся на n цифр 1.

Мы можем использовать выражение, отмеченное более ранним плакатом, чем 1 < n - 1 всегда дает нам n двоичных цифр 1 или в этом случае 1 < 3 - 1 = 0b111. Если мы рассматриваем нашу цепочку n snappers как двоичное число, в котором каждая цифра отображает вкл/выкл, и мы хотим n цифр одного, это дает нам наше представление.

Теперь мы хотим найти те случаи, когда 1 < n - 1 равно некоторому k, которое заканчивается на n двоичных цифр 1, что мы делаем, выполняя побитовое и: k и (1 < n - 1), чтобы получить последние n цифр k, а затем сравнивая это с 1 < n - 1.

Я полагаю, что этот тип мышления становится более естественным после многолетней работы с этими типами проблем, но он все еще запугивает меня, и я сомневаюсь, что когда-нибудь придумаю такое решение!

Здесь мое решение в perl:

$tests = <>;
for (1..$tests) {
    ($n, $k) = split / /, <>;
    $m = 1 << $n - 1;
    printf "Case #%d: %s\n", $_, (($k & $m) == $m) ? 'ON' : 'OFF';
}

Ответ 4

Я думаю, что мы можем распознать эту проблему, сначала вычислив ответ рукой, для некоторых рядов N (например, 1,2,3,...). После этого мы узнаем изменение состояния, а затем напишем функцию для автоматизации процесса (первая функция). Запустите программу для некоторых входов и обратите внимание на шаблон.

Когда мы получаем шаблон, напишем функцию, представляющую шаблон (вторая функция), и сравним вывод первой функции и второй функции.

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