Пусть S (n) будет набором чисел от 0 до n (без дубликатов, но в любом порядке). Тогда S(2n+1) = {2*s for s in S(n)} + {2*s+1 for s in S(n)}
и S(2n) = {2*s for s in S(n)} + {2*s+1 for s in S(n-1)}
.
Два примера:
S(7) = {2*s for s in S(3)} + {2*s+1 for s in S(3)}
= {0, 2, 4, 6} + {1, 3, 5, 7}
S(10) = {2*s for s in S(5)} + {2*s+1 for s in S(4)}
= {0, 2, 4, 6, 8, 10} + {1, 3, 5, 7, 9}
Допуская, что a(n)
определяется как сумма битов, установленных во всех числах в S(n)
, и используя формулы для S
, мы имеем a(2n+1) = 2a(n) + n+1
и a(2n) = a(n) + a(n-1) + n
. Это связано с тем, что количество битов, установленных в {2*s for s in S(n)}
, равно числу битов, установленных в S(n)
, а количество битов, заданных в {2*s+1 for s in S(n)}
, равно числу битов, заданных в S(n)
, плюс один для каждого элемент S(n)
(а именно: n+1
).
Те же уравнения появляются в https://oeis.org/A000788,, зачисленных Ральфу Стефану:
a(0) = 0
a(2n) = a(n)+a(n-1)+n
a(2n+1) = 2a(n)+n+1
Используя это, можно написать функцию B
с помощью B(N) = a(N), a(N-1)
:
def B(N):
if N == 0:
return 0, 0
r, s = B(N//2)
if N % 2:
return 2*r+N//2+1, r+s+N//2
else:
return r+s+N//2, 2*s+N//2
Двойное возвращаемое значение - это форма динамического программирования, позволяющая избежать повторного вычисления одних и тех же значений несколько раз.
Второе возвращаемое значение - это то, что вас интересует. Например:
>> print(B(7)[1])
9
>> print(B(28)[1])
64
>> print(B(10**20)[1])
3301678091638143975424
Очевидно, что это выполняется в арифметических операциях O (log N) и использует стек O (log N).
Получение к постоянной космической сложности
Можно немного уменьшить сложность пространства до O (1).
Мы можем записать уравнения Ральфа Стефана в матричной векторной форме:
[ a(2n+1) ] = [2 0 1 1] [ a(n) ]
[ a(2n) ] [1 1 1 0] * [ a(n-1)]
[ 2n+1 ] [0 0 2 1] [ n ]
[ 1 ] [0 0 0 1] [ 1 ]
и
[ a(2n) ] = [1 1 1 0] [ a(n) ]
[ a(2n-1) ] [0 2 1 0] * [ a(n-1)]
[ 2n ] [0 0 2 0] [ n ]
[ 1 ] [0 0 0 1] [ 1 ]
Повторное применение одного или другого из этих правил дает:
[ a(n) ] = M[0] * M[1] * ... * M[k] * [ a(0) ]
[ a(n-1)] [ a(-1)]
[ n ] [ 0 ]
[ 1 ] [ 1 ]
Где M[0]
, M[1]
, ..., M[k]
- это одна или другая из двух матриц 4x4, которые встречаются в версиях матриц-векторов уравнений Ральфа Стефана, в зависимости от k
бит n
.
Таким образом:
def mat_mul(A, B):
C = [[0] * 4 for _ in range(4)]
for i in range(4):
for j in range(4):
for k in range(4):
C[i][k] += A[i][j] * B[j][k]
return C
M1 = [[2, 0, 1, 1], [1, 1, 1, 0], [0, 0, 2, 1], [0, 0, 0, 1]]
M0 = [[1, 1, 1, 0], [0, 2, 1, 0], [0, 0, 2, 0], [0, 0, 0, 1]]
def B2(N):
M = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]
while N:
M = mat_mul(M, M1 if N%2 else M0)
N >>= 1
return M[1][3]
Функция B2
выполняет O (log n) арифметическую операцию, но использует постоянное пространство.
Мы можем сделать немного лучше, отметив, что матрица M
всегда имеет вид:
[ a b c d ]
[ a-1 b+1 c e ]
[ 0 0 a+b a-1 ]
[ 0 0 0 1 ]
Затем B3
оптимизирует матричные умножения на B2
в зависимости от наблюдаемой структуры M
:
def B3(N):
a, b, c, d, e = 1, 0, 0, 0, 0
while N:
if N%2:
a, c, d, e = 2*a+b, a+b+2*c, a+c+d, a+c+e-1
else:
b, c = a+2*b, a+b+2*c
N >>= 1
return e
Это так хорошо, как этот подход может нас занять: единственными арифметическими операциями являются сложения, умножение на два, деление на два и тестирование младшего бита. Пространственная сложность постоянна. Даже для огромных N
(например, 10 ^ 200) затраченное время незначительно.
Быстрая версия на C.
Для скорости версия C (использующая расширение gcc для __int128) вычисляет b3(10**20)
примерно за 140 наносекунд на моей машине. Код представляет собой простое преобразование Python-функции B3
(отмечается, что d
не требуется), слегка затрудненный отсутствием множественного присваивания в C.
typedef unsigned __int128 uint128;
uint128 b3(uint128 n) {
uint128 a=1, b=0, c=0, e=0;
while (n) {
if (n&1) {
e = a+c+e-1;
c = a+b+2*c;
a = 2*a+b;
} else {
c = a+b+2*c;
b = a+2*b;
}
n >>= 1;
}
return e;
}