Получение 39 байтов
Это объяснение того, как я получил 39-байтовое решение, которое Деннис и Джонатан Фрех также нашли отдельно. Или, скорее, это объясняет, как можно прийти к ответу задним числом, намного лучше, чем мой настоящий путь к нему, который был полон грязных рассуждений и тупиков.
n=0
exec"print n;n=n+2^-(n+2^n)%3;"*400
Написание этого немного меньше в гольфе и с большим количеством паренов, это выглядит так:
n=0
for _ in range(400):
print n
n=(n+2)^(-((n+2)^n))%3
Битовые соотношения
Мы начнем с идеи из моего 47-байтового решения, чтобы вывести все числа в форме, в n=2*k+bкоторой происходит kобратный отсчет, 0,1,...,399и bэто бит четности, который делает общее число равным 1.
Напишем par(x)для бита четности из x, то есть XOR ( ^) все биты в x. Это 0, если есть четное число 1-бит (число - зло), и 1, если есть нечетное число 1-бит. Ибо n=2*k+bмы имеем par(n) = par(k)^b, поэтому для достижения зла par(n)==0нам нужно b=par(k), то есть последний бит nдолжен быть четным битом предыдущих битов.
Мои первые попытки были играть в гольф на экспрессирующие par(k), сначала непосредственно с bin(k).count('1')%2, а затем с битами .
Обновления четности
Тем не менее, казалось, не было короткого выражения. Вместо этого это помогло понять, что есть больше информации для работы. Вместо того, чтобы просто вычислять битовую четность текущего числа,
k ----> par(k)
мы можем обновить битовую четность при увеличении kдо k+1.
k ----> par(k)
|
v
k+1 ----> par(k+1)
То есть, так как мы считаем k=0,1,2,..., нам просто нужно поддерживать текущий битовый паритет, а не вычислять его с нуля каждый раз. Обновление четности битов par(k+1)^par(k)- это четность количества бит, перевернутых при переходе от kк k+1, то есть par((k+1)^k).
par(k+1) ^ par(k) = par((k+1)^k)
par(k+1) = par(k) ^ par((k+1)^k)
Форма (k+1)^k
Теперь нам нужно вычислить par((k+1)^k). Может показаться, что мы ничего не получили, потому что вычисление четности битов - это именно та проблема, которую мы пытаемся решить. Но числа, выраженные как, (k+1)^kимеют форму 1,3,7,15,.., которая на единицу меньше степени 2, факт, часто используемый в битовых хакерских атаках . Давайте посмотрим, почему это так.
Когда мы увеличиваем k, эффект двоичных переносов состоит в том, чтобы инвертировать последние 0и все 1справа, создавая новое ведение, 0если их не было. Например, взятьk=43=0b101011
**
101011 (43)
+ 1
------
= 101100 (44)
101011 (43)
^101100 (44)
------
= 000111 (77)
Столбцы, вызывающие перенос, отмечены значком *. Они 1изменяют на a 0и передают бит переноса 1, который продолжает распространяться влево до тех пор, пока не попадет 0в k, который изменится на 1. Любые биты дальше слева не затрагиваются. Таким образом, когда k^(k+1)проверяет , какие позиции бит изменить , kчтобы k+1он находит позиции крайнего правые 0и в 1«с до его права. То есть измененные биты образуют суффикс, поэтому результатом являются 0, за которыми следует одна или несколько единиц. Без ведущих нулей существуют двоичные числа 1, 11, 111, 1111, ..., которые на единицу меньше степени 2.
вычисления par((k+1)^k)
Теперь, когда мы понимаем, что (k+1)^kэто ограничено 1,3,7,15,..., давайте найдем способ вычислить битовую четность таких чисел. Здесь, полезный факт, что 1,2,4,8,16,...чередуются по модулю 3между 1и 2, так как 2==-1 mod 3. Итак, взятие 1,3,7,15,31,63...по модулю 3дает 1,0,1,0,1,0..., что в точности соответствует их битовым соотношениям. Отлично!
Итак, мы можем сделать обновление par(k+1) = par(k) ^ par((k+1)^k)как
par(k+1) = par(k) ^ ((k+1)^k)%3
Используя bв качестве переменной, в которой мы сохраняем четность, это выглядит так
b^=((k+1)^k)%3
Написание кода
Собирая это вместе в коде, мы начинаем kи бит четности bкак в 0, затем многократно печатаем n=2*k+bи обновляем b=b^((k+1)^k)%3и k=k+1.
46 байт
k=b=0
exec"print 2*k+b;b^=(k+1^k)%3;k+=1;"*400
Попробуйте онлайн!
Мы убрали скобки вокруг k+1в ((k+1)^k)%3потому , что Python старшинство делает добавление первой в любом случае, странно , как это выглядит.
Улучшения кода
Хотя мы можем добиться большего, работая напрямую с одной переменной n=2*k+bи выполняя обновления непосредственно над ней. Делать k+=1соответствует n+=2. И, обновление b^=(k+1^k)%3соответствует n^=(k+1^k)%3. Здесь, k=n/2перед обновлением n.
44 байта
n=0
exec"print n;n^=(n/2+1^n/2)%3;n+=2;"*400
Попробуйте онлайн!
Мы можем сократить n/2+1^n/2(помните, что это (n/2+1)^n/2), переписав
n/2+1 ^ n/2
(n+2)/2 ^ n/2
(n+2 ^ n)/2
Так как /2удаляет последний бит, не имеет значения, делаем ли мы это до или после копирования. Итак, у нас есть n^=(n+2^n)/2%3. Мы можем сохранить еще один байт, отметив , что по модулю 3, /2эквивалентно *2эквивалентно -, отметив , что n+2^nдаже поэтому разделение актуально сращивание без напольного покрытия. Это даетn^=-(n+2^n)%3
41 байт
n=0
exec"print n;n^=-(n+2^n)%3;n+=2;"*400
Попробуйте онлайн!
Наконец, мы можем объединить операции n^=c;n+=2в n=(n+2)^c, где cнемного. Это работает, потому что ^cдействует только на последний бит и +2не заботится о последнем бите, поэтому операции коммутируют. Опять же, приоритет позволяет нам опускать парены и писать n=n+2^c.
39 байт
n=0
exec"print n;n=n+2^-(n+2^n)%3;"*400
Попробуйте онлайн!