Первое появление в диатомической секвенции Стерна
Вы получаете целое число n, и вам нужно найти индекс его первого появления в диатомической последовательности Стерна.
Последовательность определяется следующим образом:
a[0] = 0
a[1] = 1
a[2*i] = a[i]
a[2*i+1] = a[i] + a[i+1]
Смотрите MathWorld.
Поскольку n может быть до 400000, это не очень хорошая идея, особенно потому, что ограничение по времени составляет 4000 мс.
Последовательность довольно странная: первое вхождение 8 - 21, но первое вхождение 6 - 33.
Есть идеи как решить это?
Может быть, это может помочь: OEIS
2 ответа
Мы можем легко решить для первого появления числа в диапазоне 400000 менее чем за четыре секунды:
Prelude Diatomic> firstDiatomic 400000
363490989
(0.03 secs, 26265328 bytes)
Prelude Diatomic> map firstDiatomic [400000 .. 400100]
[363490989,323659475,580472163,362981813,349334091,355685483,346478235,355707595
,291165867,346344083,347155797,316314293,576398643,315265835,313171245,355183267
,315444051,315970205,575509833,311741035,340569429,313223987,565355925,296441165
,361911645,312104147,557145429,317106853,323637939,324425077,610613547,311579309
,316037811,311744107,342436533,348992869,313382235,325406123,355818699,312128723
,347230875,324752171,313178421,312841811,313215645,321754459,576114987,325793195
,313148763,558545581,355294101,359224397,345462093,307583675,355677549,312120731
,341404245,316298389,581506779,345401947,312109779,316315061,315987123,313447771
,361540179,313878107,304788843,325765547,316036275,313731751,355635795,312035947
,346756533,313873883,349358379,357393763,559244877,313317739,325364139,312128107
,580201947,358182323,314944173,357403987,584291115,312158827,347448723,363246413
,315935571,349386085,315929427,312137323,357247725,313207657,320121429,356954923
,557139285,296392013,576042123,311726765,296408397]
(2.45 secs, 3201358192 bytes)
Ключ к этому - дерево Калкин-Уилф.
Начиная с дроби 1/1
, это построено по правилу, что для узла с дробью a/b
, его левый ребенок несет дробь a/(a+b)
и правая дочерняя дробь (a+b)/b
,
1/1
/ \
/ \
/ \
1/2 2/1
/ \ / \
1/3 3/2 2/3 3/1
и т. д. Двухатомная последовательность (начиная с индекса 1) - это последовательность числителей дробей в дереве Калкина-Уилфа, когда они проходят уровень за уровнем, каждый уровень слева направо.
Если мы посмотрим на дерево индексов
1
/ \
/ \
/ \
2 3
/ \ / \
4 5 6 7
/ \
8 9 ...
мы можем легко проверить, что узел по индексу k
в дереве Калкин-Уилф несет фракцию a[k]/a[k+1]
по индукции.
Это очевидно верно для k = 1
(a[1] = a[2] = 1
) и с тех пор
за
k = 2*j
у нас есть левый потомок узла с индексомj
так что дробьa[j]/(a[j]+a[j+1])
а такжеa[k] = a[j]
а такжеa[k+1] = a[j] + a[j+1]
являются определяющими уравнениями последовательности.за
k = 2*j+1
у нас есть правильный дочерний элемент узла с индексомj
так что дробь(a[j]+a[j+1])/a[j+1]
и этоa[k]/a[k+1]
снова по определяющим уравнениям.
Все положительные редуцированные дроби встречаются ровно один раз в дереве Калкина-Уилфа (оставлено в качестве упражнения для читателя), поэтому все положительные целые числа встречаются в двухатомной последовательности.
Мы можем найти узел в дереве Калкина-Уилфа по индексу, следуя двоичному представлению индекса, от старшего значащего к младшему, для 1-битного мы переходим к правому дочернему элементу и для 0-битного к левый. (Для этого неплохо дополнить дерево Калкина-Уилфа узлом 0/1
чей правый ребенок 1/1
узел, так что нам нужно иметь шаг для самого значительного установленного бита индекса.)
Теперь, это еще не очень помогает решить проблему под рукой.
Но давайте сначала решим связанную проблему: для уменьшенной дроби p/q
определите его индекс.
Предположим, что p > q
, Тогда мы знаем, что p/q
является правильным ребенком, и его родитель (p-q)/q
, Если также p-q > q
, у нас снова есть правильный ребенок, чей родитель (p - 2*q)/q
, Продолжая, если
p = a*q + b, 1 <= b < q
тогда мы достигаем p/q
узел из b/q
узел, перейдя к нужному ребенку a
раз.
Теперь нам нужно найти узел, числитель которого меньше, чем его знаменатель. Это, конечно, левый потомок своего родителя. Родитель b/q
является b/(q-b)
затем. Если
q = c*b + d, 1 <= d < b
мы должны идти к левому ребенку c
раз от узла b/d
достигать b/q
,
И так далее.
Мы можем найти путь от корня (1/1
) к p/q
узел, использующий непрерывную дробь (здесь я рассматриваю только простые цепные дроби), расширение p/q
, Позволять p > q
а также
p/q = [a_0, a_1, ..., a_r,1]
продолжающееся расширение фракции p/q
кончающийся на 1
,
- Если
r
ровно, то иди к нужному ребенкуa_r
раз, то налевоa_(r-1)
раз, тогда к правильному ребенку... тогдаa_1
раз слева от ребенка, и, наконец,a_0
раз вправо. - Если
r
странно, тогда сначала идите к левому ребенкуa_r
раз, тоa_(r-1)
раз вправо... тогдаa_1
раз слева от ребенка, и, наконец,a_0
раз вправо.
За p < q
, мы должны закончить идти налево, следовательно, начать идти налево даже r
и начать идти вправо для нечетных r
,
Таким образом, мы обнаружили тесную связь между двоичным представлением индекса и продолжающимся расширением дроби дроби, переносимой узлом по пути от корня к узлу.
Пусть код длины-длины индекса k
быть
[c_1, c_2, ..., c_j] (all c_i > 0)
то есть двоичное представление k
начинается с c_1
те, сопровождаемые c_2
нули, то c_3
и т. д. и заканчивая c_j
- те, если
k
странно - следовательноj
тоже странно; - нули, если
k
ровно - значитj
тоже чётно.
затем [c_j, c_(j-1), ..., c_2, c_1]
является продолженным расширением фракции a[k]/a[k+1]
чья длина имеет такое же соотношение, как k
(у каждого рационального есть ровно два непрерывных дробных разложения, одно с нечетной длиной, другое с четной длиной).
RLE дает путь от 0/1
узел выше 1/1
в a[k]/a[k+1]
, Длина пути
- количество битов, необходимых для представления
k
, а также - сумма частных отношений в продолженном разложении дроби.
Теперь, чтобы найти индекс первого появления n > 0
в двухатомной последовательности мы сначала заметим, что наименьший индекс обязательно должен быть нечетным, поскольку a[k] = a[k/2]
даже для k
, Пусть наименьший индекс будет k = 2*j+1
, затем
- длина RLE
k
странно, - дробь в узле с индексом
k
являетсяa[2*j+1]/a[2*j+2] = (a[j] + a[j+1])/a[j+1]
следовательно, это правильный ребенок.
Так что наименьший индекс k
с a[k] = n
соответствует крайнему левому окончанию всех кратчайших путей к узлу с числителем n
,
Кратчайшие пути соответствуют продолженным дробным разложениям n/m
, где 0 < m <= n
взаимно n
[дробь должна быть уменьшена] с наименьшей суммой частных отношений.
Какой длины мы должны ожидать? Учитывая продолжение фракции p/q = [a_0, a_1, ..., a_r]
с a_0 > 0
и сумма
s = a_0 + ... + a_r
числитель p
ограничен F(s+1)
и знаменатель q
от F(s)
, где F(j)
это j
число Фибоначчи. Границы четкие, для a_0 = a_1 = ... = a_r = 1
фракция F(s+1)/F(s)
,
Так что если F(t) < n <= F(t+1)
сумма частных отношений продолжения расширения дроби (любого из двух) равна >= t
, Часто есть m
такой, что сумма частных частных продолжения дроби n/m
это точно t
, но не всегда:
F(5) = 5 < 6 <= F(6) = 8
и продолженные расширения фракций двух восстановленных фракций 6/m
с 0 < m <= 6
являются
6/1 = [6] (alternatively [5,1])
6/5 = [1,4,1] (alternatively [1,5])
с суммой частичных отношений 6. Однако наименьшая возможная сумма частных отношений никогда не бывает намного больше (самое большое, что я знаю, это t+2
).
Продолжение дробных расширений n/m
а также n/(n-m)
тесно связаны. Давайте предположим, что m < n/2
, и разреши
n/m = [a_0, a_1, ..., a_r]
затем a_0 >= 2
,
(n-m)/m = [a_0 - 1, a_1, ..., a_r]
и с тех пор
n/(n-m) = 1 + m/(n-m) = 1 + 1/((n-m)/m)
продолжающееся расширение фракции n/(n-m)
является
n/(n-m) = [1, a_0 - 1, a_1, ..., a_r]
В частности, сумма частных отношений одинакова для обоих.
К сожалению, я не знаю, как найти m
с наименьшей суммой частных отношений без грубой силы, поэтому алгоритм n > 2
за
0 < m < n/2
взаимноn
найти непрерывное расширение дробиn/m
, собирая те с наименьшей суммой частных отношений (обычный алгоритм производит разложения, последний частный коэффициент которых> 1
, мы предполагаем, что).Отрегулируйте найденные расширения продолженных дробей [их не много] следующим образом:
- если CF
[a_0, a_1, ..., a_r]
имеет четную длину, преобразовать его в[a_0, a_1, ..., a_(r-1), a_r - 1, 1]
- в противном случае используйте
[1, a_0 - 1, a_1, ..., a_(r-1), a_r - 1, 1]
(который выбирает тот, между
n/m
а такжеn/(n-m)
приводя к меньшему индексу)- если CF
поменяйте местами дробные дроби, чтобы получить кодировки длин серий соответствующих индексов
выберите самый маленький из них.
На шаге 1 полезно использовать наименьшую найденную сумму для сокращения.
Код (Haskell, так как это проще всего):
module Diatomic (diatomic, firstDiatomic, fuscs) where
import Data.List
strip :: Int -> Int -> Int
strip p = go
where
go n = case n `quotRem` p of
(q,r) | r == 0 -> go q
| otherwise -> n
primeFactors :: Int -> [Int]
primeFactors n
| n < 1 = error "primeFactors: non-positive argument"
| n == 1 = []
| n `rem` 2 == 0 = 2 : go (strip 2 (n `quot` 2)) 3
| otherwise = go n 3
where
go 1 _ = []
go m p
| m < p*p = [m]
| r == 0 = p : go (strip p q) (p+2)
| otherwise = go m (p+2)
where
(q,r) = m `quotRem` p
contFracLim :: Int -> Int -> Int -> Maybe [Int]
contFracLim = go []
where
go acc lim n d = case n `quotRem` d of
(a,b) | lim < a -> Nothing
| b == 0 -> Just (a:acc)
| otherwise -> go (a:acc) (lim - a) d b
fixUpCF :: [Int] -> [Int]
fixUpCF [a]
| a < 3 = [a]
| otherwise = [1,a-2,1]
fixUpCF xs
| even (length xs) = case xs of
(1:_) -> fixEnd xs
(a:bs) -> 1 : (a-1) : bs
| otherwise = case xs of
(1:_) -> xs
(a:bs) -> 1 : fixEnd ((a-1):bs)
fixEnd :: [Int] -> [Int]
fixEnd [a,1] = [a+1]
fixEnd [a] = [a-1,1]
fixEnd (a:bs) = a : fixEnd bs
fixEnd _ = error "Shouldn't have called fixEnd with an empty list"
cfCompare :: [Int] -> [Int] -> Ordering
cfCompare (a:bs) (c:ds) = case compare a c of
EQ -> cfCompare ds bs
cp -> cp
fibs :: [Integer]
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
toNumber :: [Int] -> Integer
toNumber = foldl' ((+) . (*2)) 0 . concat . (flip (zipWith replicate) $ cycle [1,0])
fuscs :: Integer -> (Integer, Integer)
fuscs 0 = (0,1)
fuscs 1 = (1,1)
fuscs n = case n `quotRem` 2 of
(q,r) -> let (a,b) = fuscs q
in if r == 0
then (a,a+b)
else (a+b,b)
diatomic :: Integer -> Integer
diatomic = fst . fuscs
firstDiatomic :: Int -> Integer
firstDiatomic n
| n < 0 = error "Diatomic sequence has no negative terms"
| n < 2 = fromIntegral n
| n == 2 = 3
| otherwise = toNumber $ bestCF n
bestCF :: Int -> [Int]
bestCF n = check [] estimate start
where
pfs = primeFactors n
(step,ops) = case pfs of
(2:xs) -> (2,xs)
_ -> (1,pfs)
start0 = (n-1) `quot` 2
start | even n && even start0 = start0 - 1
| otherwise = start0
eligible k = all ((/= 0) . (k `rem`)) ops
estimate = length (takeWhile (<= fromIntegral n) fibs) + 2
check candidates lim k
| k < 1 || n `quot` k >= lim = if null candidates
then check [] (2*lim) start
else minimumBy cfCompare candidates
| eligible k = case contFracLim lim n k of
Nothing -> check candidates lim (k-step)
Just cf -> let s = sum cf
in if s < lim
then check [fixUpCF cf] s (k - step)
else check (fixUpCF cf : candidates) lim (k-step)
| otherwise = check candidates lim (k-step)
Я бы порекомендовал вам прочитать это письмо от Дейкстры, которое объясняет альтернативный способ вычисления этой функции с помощью:
n, a, b := N, 1, 0;
do n ≠ 0 and even(n) → a, n:= a + b, n/2
odd(n) → b, n:= b + a, (n-1)/2
od {b = fusc(N)}
Это начинается с a,b=1,0 и эффективно использует последовательные биты N (от наименьшего до наиболее значимого), чтобы увеличить a и b, причем конечным результатом является значение b.
Следовательно, индекс первого появления определенного значения для b может быть вычислен путем нахождения наименьшего n, для которого эта итерация приведет к этому значению b.
Один из способов найти это наименьшее n - это использовать поиск A*, где стоимость - это значение n. Эффективность алгоритма будет зависеть от выбранного вами эвристического алгоритма.
Для эвристики я бы рекомендовал отметить, что:
- конечное значение всегда будет кратно gcd(a,b) (это может быть использовано для исключения некоторых узлов, которые никогда не могут создать цель)
- б всегда увеличивается
- существует максимальная (экспоненциальная) скорость, с которой b может увеличиваться (скорость зависит от текущего значения a)
РЕДАКТИРОВАТЬ
Вот пример кода Python, иллюстрирующий подход A*.
from heapq import *
def gcd(a,b):
while a:
a,b=b%a,a
return b
def heuristic(node,goal):
"""Estimate least n required to make b==goal"""
n,a,b,k = node
if b==goal: return n
# Otherwise needs to have at least one more bit set
# Improve this heuristic to make the algorithm faster
return n+(1<<k)
def diatomic(goal):
"""Return index of first appearance of n in Stern's Diatomic sequence"""
start=0,1,0,0
f_score=[] # This is used as a heap
heappush(f_score, (0,start) )
while 1:
s,node = heappop(f_score)
n,a,b,k = node
if b==goal:
return n
for node in [ (n,a+b,b,k+1),(n+(1<<k),a,b+a,k+1) ]:
n2,a2,b2,k2 = node
if b2<=goal and (goal%gcd(a2,b2))==0:
heappush(f_score,(heuristic(node,goal),node))
print [diatomic(n) for n in xrange(1,10)]