Допоможіть, я потрапив у пастку трикутника Серпінського!


44

Намалювання трикутника Серпінського було зроблено до смерті . Є й інші цікаві речі, які ми можемо з цим зробити. Якщо ми досить сильно косимося до трикутника, ми можемо розглядати перевернуті трикутники як вузли фрактального графіка. Давайте знайдемо цей графік!

Спочатку давайте призначимо номер кожному вузлу. Найбільший трикутник догори дном буде нульовим вузлом, і тоді ми просто спускаємося по шару вниз (по ширині перший), присвоюючи послідовні числа в порядку зверху ліворуч праворуч:

введіть тут опис зображення
Клацніть для більшої версії, де невеликі цифри трохи менше розмито.

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

Як ми рухаємося навколо цього графіка? Існує до шести природних кроків, які можна зробити з будь-якого даного трикутника:

  • Завжди можна переміщатися через середину одного з ребер до одного з трьох дітей поточного вузла. Ми будемо позначати ці кроки як N, SWі SE. Наприклад , якщо ми в даний час на вузлі 2, це призведе до вузлів 7, 8, 9відповідно. Інші рухи по краях (до непрямих нащадків) заборонені.
  • Можна також просуватися через один з трьох кутів, за умови, що він не торкається краю трикутника, ні до прямого батьків, ні до одного з двох непрямих предків. Ми будемо позначати ці кроки як S, NEі NW. Наприклад , якщо ми в даний час на вузлі 31, Sпризведе до 10, NEбуло б неприпустимим і NWпризведе до 0.

Змагання

Дано два невід’ємні цілі числа xі yзнайдіть найкоротший шлях від xдо y, використовуючи лише шість описаних вище кроків. Якщо є кілька найкоротших шляхів, виведіть будь-який з них.

Зауважте, що ваш код повинен працювати більше, ніж лише 5 рівнів, зображених на наведеній вище діаграмі. Ви можете припустити, що x, y < 1743392200. Це гарантує, що вони вміщуються в 32-бітове ціле число. Зауважте, що це відповідає 20 рівням дерева.

Ваш код повинен обробити будь-який дійсний вхід менш ніж за 5 секунд . У той час як це виключає найперший пошук в широкій силі, це повинно бути досить слабким обмеженням - моя опорна реалізація обробляє довільне введення на глибину 1000 за півсекунди (це ~ 480-значне число для вузлів).

Ви можете написати програму або функцію, взявши введення через STDIN (або найближчу альтернативу), аргумент командного рядка або аргумент функції та вивівши результат через STDOUT (або найближчу альтернативу), значення повернення функції або параметр функції (out).

Вихід повинен бути плоским, недвозначним список рядків N, S, NE, NW, SE, SW, використовуючи будь-який розумний роздільник (прогалини, символи перекладу рядка, коми, ","...).

Діють стандартні правила .

Випробування

Перші кілька тестових випадків можна опрацювати вручну, використовуючи схему, подану вище. Інші забезпечують достатню ефективність відповідей. Для них можуть бути інші рішення такої ж довжини, які не вказані.

0 40                    => N N N N
66 67                   => S SW N N N
30 2                    => NW NW -or- NE SW
93 2                    => NE SW
120 61                  => NW NW NW NW N SE SW N
1493682877 0            => S S NW NW
0 368460408             => SW SW N N SW SW SE SW SW N SE N N SW SW N SE SE
1371432130 1242824      => NW NW NE NW N SE SW SW SW SE SE SW N N N N SW
520174 1675046339       => NE NW NE NE SE SE SW SW N SE N SW N SW SE N N N N SE SE SW SW
312602548 940907702     => NE NW S SW N N SW SE SE SE SW SE N N SW SE SE SE SW
1238153746 1371016873   => NE NE NE SE N N SW N N SW N SE SE SW N SW N N SE N SE N
547211529 1386725128    => S S S NE NW N N SE N SW N SE SW SE SW N SE SE N SE SW SW N
1162261466 1743392199   => NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE

Відповіді:


5

Рубі, 195 194 190 184 байт

Оригінал: З вибаченнями Елл , оскільки це, по суті, портрет їхньої відповіді, і дякую Дорнобу за допомогу в налагодженні цієї відповіді. Мабуть, є ще один алгоритм цієї проблеми - щось із цим *f[x[0,**however far x matches with y**],y]- але я збережу це ще в інший час.

a=->n{n<1?[]:a[~-n/3]+[-n%3]}
f=->x,y{j=x.size
(Array===x)?x==y[0,j]?y[j..-1].map{|m|%w[SE SW N][m]}:x.uniq.map{|m|[%w[NW NE S][m],*f[x[0,x.rindex(m)],y]]}.min_by(&:size):f[a[x],a[y]]}

EDIT: жадібний алгоритм не працює h[299792458, 1000000]. Я повернув кількість байтів до 195, коли я ще раз відновлював свій алгоритм. Виправлено це лише для того, щоб кількість байтів піднялася до 203. Зітхніть

Розробляється: Ця програма використовує жадібний алгоритм для пошуку спільних предків x[0,j]==y[0,j](зверніть увагу: може бути декілька загальних предків). Алгоритм дуже вільно базується на рекурсивному пошуку предків Елла . Перша половина отриманих вказівок полягає в тому, як дістатися до цього загального предка, а друга половина - до y на основі y[j..-1].

Примітка: a[n]тут повертається базове-3 біективне нумерація, використовуючи 2,1,0замість цифр 1,2,3.

Як приклад, давайте пропустимо через f[59,17]або f[[2,0,2,1],[2,1,1]]. Тут j == 1. Щоб дістатися x[0,j], ми їдемо 0або NW. Потім, щоб дістатися y, поїхати [1,1]абоSW SW

a=->n{n<1?[]:a[~-n/3]+[-n%3]}
h=->m,n{x=a[m];y=a[n];c=[];j=x.size
(j=x.uniq.map{|m|k=x.rindex(m);x[0..k]==y[0..k]?j:k}.min
c<<%w[NW NE S][x[j]];x=x[0,j])until x==y[0,j]
c+y[j..-1].map{|m|%w[SE SW N][m]}}

45

Пітон 2, 208 205 200 байт

A=lambda n:n and A(~-n/3)+[-n%3]or[]
f=lambda x,y:f(A(x),A(y))if x<[]else["SSNEW"[m::3]for m in
y[len(x):]]if x==y[:len(x)]else min([["NNSWE"[m::3]]+f(x[:~x[::-1].index(m)],y)for
m in set(x)],key=len)

Функція, яка fбере пару номерів вузлів і повертає найкоротший шлях у вигляді списку рядків.

Пояснення

Почнемо з використання іншої схеми адресації трикутників; адреса кожного трикутника - це рядок, визначений так:

  • Адреса центрального трикутника - порожній рядок.

  • Адреси на півночі, південний захід і південний схід дітей кожного трикутника утворені додаванням 0, 1і 2, відповідно, за адресою трикутника.

По суті, адреса кожного трикутника кодує (найкоротший) шлях від центрального трикутника до нього. Перше, що робить наша програма - це перевести номери трикутника введення на відповідні адреси.

Фігура 1

Клацніть на зображення для збільшення версії.

Можливі рухи в кожному трикутнику легко визначаються з адреси:

  • Для переміщення на північ, північний захід і південний схід дітей, ми просто додати 0, 1і 2, відповідно, за адресою.

  • Для того, щоб перейти на південь, північний схід, і північний захід предки, ми знаходимо останній (крайній правий) виникнення 0, 1і 2, відповідно, і обрізати адреса зліва від нього. Якщо немає 0, 1або 2в адресі, то відповідний предок не існує. Наприклад, щоб перейти до північно-західного предка 112(тобто його батька), ми знаходимо останнє явище 2в 112, яке є останнім символом, і обрізаємо адресу зліва від нього, даючи нам 11; щоб перейти до північно-східного предка, ми знаходимо останнє явище 1в 112, яке є другим символом, і обрізаємо адресу зліва від нього, даючи нам 1; однак, 112не має південного предка, оскільки його немає0 на свою адресу.

Зауважте кілька речей про пару адрес, xа також y:

  • Якщо xє початковою підрядкою y, то yє нащадком x, і тому найкоротший шлях від xдо yпросто слідує за відповідною дочірньою частиною кожного трикутника між xі y; Іншими словами, ми можемо замінити кожен 0, 1і 2в y[len(x):]с N, SWі SE, відповідно.

  • В іншому випадку нехай iбуде індекс першої невідповідності між xі y. Там немає шляху з xв yтому , що не проходить через x[:i](який так само , як y[:i]), тобто, перший загальний предок xі y. Отже, будь-який шлях від xдо yповинен пройти x[:i]або хтось із його предків, назвемо цей трикутник z, а потім продовжимо y. Щоб прибути з xдо z, ми слідуємо за предками, як описано вище. Найкоротший шлях від zдо yзадається попередньою точкою кулі.

Якщо xце початкова підрядка y, то найкоротший шлях від xдо неї yлегко задається першою точкою кулі вище. В іншому випадку, ми дозволяємо jбути найменшим з показників останніх появ 0, 1і 2в x. Якщо jбільше або дорівнює, індекс першого розбіжності xі y, iми просто додати відповідний крок ( S, NEабо NW, відповідно) на шляху обрізки xвліво від j, і по- , як і раніше. Речі стають складнішими, якщо jменше i, оскільки тоді ми можемо досягти yнайшвидшого підйому безпосередньо до загального предка x[:j]і спустившись аж доyабо ми зможемо потрапити до іншого спільного предка, xі yце ближче до yпідйому до іншого предка xправоруч iі yшвидше дістатися звідти . Наприклад, щоб дістатися 1222до 1, найкоротший шлях - це спершу піднятися до центрального трикутника (адреса якого - порожній рядок), а потім спуститися до 1, тобто перший хід відведе нас ліворуч від точки невідповідності. однак, щоб дістатися 1222до 12, найкоротший шлях - це піднятися до нього 122, а потім до 12, тобто перший хід тримає нас праворуч від точки невідповідності.

Отже, як ми знаходимо найкоротший шлях? "Офіційна" програма використовує грубу силу підходу, намагаючись зробити всі можливі кроки до будь-якого з предків, коли xце не є початковим підрядком y. Це не так погано, як це звучить! Він вирішує всі тестові випадки, комбіновані, протягом секунди чи двох.

Але знову ж таки, ми можемо зробити набагато краще: якщо є більше одного прямодоступного предка ліворуч від точки невідповідності, нам потрібно лише протестувати правий крайній правий, і якщо є більше одного безпосередньо пращура предків до праворуч від точки невідповідності нам потрібно лише перевірити крайній лівий. Це дає лінійний алгоритм часу, довжина wrt x(тобто глибина трикутника джерела, або час, пропорційний логарифму числа вихідного трикутника), який збільшує масштабність навіть набагато більших тестових випадків. Наступна програма реалізує цей алгоритм, принаймні по суті - завдяки гольфу його складність насправді гірша, ніж лінійна, але вона все ще дуже швидка.

Пітон 2, 271 266 261 байт

def f(x,y):
 exec"g=f;f=[]\nwhile y:f=[-y%3]+f;y=~-y/3\ny=x;"*2;G=["SSNEW"[n::3]for
n in g];P=G+f;p=[];s=0
 while f[s:]:
    i=len(f)+~max(map(f[::-1].index,f[s:]));m=["NNSWE"[f[i]::3]]
    if f[:i]==g[:i]:P=min(p+m+G[i:],P,key=len);s=i+1
    else:p+=m;f=f[:i]
 return P

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

Результати

Наступний фрагмент може бути використаний для запуску тестів для будь-якої версії та отримання результатів:

def test(x, y, length):
    path = f(x, y)
    print "%10d %10d  =>  %2d: %s" % (x, y, len(path), " ".join(path))
    assert len(path) == length

#         x           y        Length
test(          0,          40,    4   )
test(         66,          67,    5   )
test(         30,           2,    2   )
test(         93,           2,    2   )
test(        120,          61,    8   )
test( 1493682877,           0,    4   )
test(          0,   368460408,   18   )
test( 1371432130,     1242824,   17   )
test(     520174,  1675046339,   23   )
test(  312602548,   940907702,   19   )
test( 1238153746,  1371016873,   22   )
test(  547211529,  1386725128,   23   )
test( 1162261466,  1743392199,   38   )

Версія для гольфу

         0         40  =>   4: N N N N
        66         67  =>   5: S SW N N N
        30          2  =>   2: NE SW
        93          2  =>   2: NE SW
       120         61  =>   8: NW NW NW NW N SE SW N
1493682877          0  =>   4: S S NW NW
         0  368460408  =>  18: SW SW N N SW SW SE SW SW N SE N N SW SW N SE SE
1371432130    1242824  =>  17: NW NW NE NW N SE SW SW SW SE SE SW N N N N SW
    520174 1675046339  =>  23: NE NE NE NE SE SE SW SW N SE N SW N SW SE N N N N SE SE SW SW
 312602548  940907702  =>  19: NE NW S SW N N SW SE SE SE SW SE N N SW SE SE SE SW
1238153746 1371016873  =>  22: NE NE NE SE N N SW N N SW N SE SE SW N SW N N SE N SE N
 547211529 1386725128  =>  23: S S S S NW N N SE N SW N SE SW SE SW N SE SE N SE SW SW N
1162261466 1743392199  =>  38: NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE

Ефективна версія

         0         40  =>   4: N N N N
        66         67  =>   5: S SW N N N
        30          2  =>   2: NW NW
        93          2  =>   2: NE SW
       120         61  =>   8: NW NW NW NW N SE SW N
1493682877          0  =>   4: NE S NW NW
         0  368460408  =>  18: SW SW N N SW SW SE SW SW N SE N N SW SW N SE SE
1371432130    1242824  =>  17: NW NW NE NW N SE SW SW SW SE SE SW N N N N SW
    520174 1675046339  =>  23: NE NW NE NE SE SE SW SW N SE N SW N SW SE N N N N SE SE SW SW
 312602548  940907702  =>  19: NE NW S SW N N SW SE SE SE SW SE N N SW SE SE SE SW
1238153746 1371016873  =>  22: NE NE NE SE N N SW N N SW N SE SE SW N SW N N SE N SE N
 547211529 1386725128  =>  23: S S S S NW N N SE N SW N SE SW SE SW N SE SE N SE SW SW N
1162261466 1743392199  =>  38: NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE NE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE SE

6
Чорт, що було швидко. Я не можу сказати тобі, як мені це приємно щоразу, коли я змушу тебе відповісти на одне з моїх викликів. :)
Мартін Ендер

2
@ MartinBüttner Спасибі, це величезний комплімент! FWIW, я дуже люблю вирішувати ваші завдання. Я, а може і не, почав працювати над цим, поки він ще був у пісочниці ... :)
Ell

2
Схема адресації геніальна. Це круто.
BrainSteel

1
@BrainSteel, перше, що сталося зі мною, було спробувати цю схему адресації, але побачити все, що було концептуалізовано, реалізовано та записано протягом години, - це приголомшливо. +1
рівень річки Св.

1
@Zymus Я не впевнений, що я дотримуюся, але якщо ви посилаєтесь на зображення, це не повинно відповідати ОП - це інша схема адресації, як описано у публікації.
Ell

3

APL (Dyalog Unicode) , 144 132 129 118 133 132 130 124 117 байт SBCS

Дуже дякую Вену і ННГ за їхню допомогу в гольфі в цьому фруктовому саду . ⎕IO←0. Пропозиції з гольфу вітаються.

Змінити: -12 байт завдяки Ven і ngn, змінивши спосіб nвизначення та перехід від 1-індексування до 0-індексації. -3 через виправлення помилки, коли не все було переведено на 0-індексацію. -11 байт через зміну способу Pта Qвизначення. +15 байт через виправлення проблеми, коли мій алгоритм виявився невірним, завдяки багатьом завдяки ngn за допомогу у фігурації s[⊃⍋|M-s]розділу. -2 байти від перестановки методу пошуку шляху зворотного відстеження та +1 байт до виправлення помилок. -2 байти завдяки Адаму за перестановку визначення I. -6 байт завдяки ngn від перестановки визначення 'S' 'NE' 'NW' 'N' 'SW' 'SE'та перестановки, як tвизначено (це вже не окрема змінна). -7 байт завдяки ngn від гольфу, як sвизначено.

{M←⊃⍸≠⌿↑1+P Q←⍵{(⍵/3)⊤⍺-+/3*⍳⍵}¨⌊31+⍵×2⋄(∪¨↓6 2'SSNENWNNSWSE')[P[I],3+Q↓⍨⊃⌽I←⍬{M≥≢⍵:⍺⋄(⍺∘,∇↑∘⍵)s[⊃⍋|M-s←⌽⊢.⊢⌸⍵]}P]}

Спробуйте в Інтернеті!

Пояснення помилки в алгоритмі

Основна проблема полягає в тому, що я подумав, що найкоротший шлях пройшов безпосередньо через спільного предка, і насправді не міг пройти через предка загального предка. Це невірно, оскільки продемонструють наступні приклади.

З 66 до 5

66  0 2 2 2  0 2 2 2
5   0 1      0 1
       common ancestor

The two ancestors of 0 2 2 2 are:
0 2 2
(empty)

(empty) has the shorter path back to 0 1 as it only needs two forward moves,
while 0 2 2 requires two more backtracks and one more forward move.

З 299792458 по 45687

299792458  0 2 1 1 0 1 1 2 1 1 1 2 1 0 2 2 2 0
45687      0 2 1 1 0 1 1 1 2 2
                          common ancestor

The three ancestors of 299792458 are:
0 2 1 1 0 1 1 2 1 1 1 2 1 0 2 2 2
0 2 1 1 0 1 1 2 1 1 1 2             choose this one
0 2 1 1 0 1 1 2 1 1 1 2 1 0 2 2

And the three ancestors of 0 2 1 1 0 1 1 2 1 1 1 2 are:
0 2 1 1
0 2 1 1 0 1 1 2 1 1
0 2 1 1 0 1 1 2 1 1 1

0 2 1 1 0 1 1 1 2 2     45687 for reference
              common ancestor

While it seems like `0 2 1 1` is the shorter path,
it actually results in a path that is 8 steps long
(2 backtracks, 6 forward steps to 45687).

Meanwhile, `0 2 1 1 0 1 1 2 1 1` is at an equal distance
to the common ancestor and has the following ancestors:
0 2 1 1
0 2 1 1 0 1 1 2 1
0 2 1 1 0 1 1

0 2 1 1 0 1 1 1 2 2     45687 for reference
              common ancestor

Clearly, this is the superior path, as with three backtracks, we have reached
the point of the common ancestor. With 3 backtracks and 3 forward moves,
we have a path that is 6 steps long.

Пояснення коду

                         should be an array of 2 integers, x y
SierpinskiPath←{⎕IO0   0-indexing
         P Q←{...}¨⍵   First, the bijective base-3 numeration of x and y
    P Q←{
        n←⌊31+⍵×2   The number of digits in the numeration
        z←+/3*⍳⍵     The number of numerations with  n digits
        (n/3)⊤⍵-z    And a simple decode  (base conversion) of ⍵-z
    }¨⍵              gets us our numerations, our paths

    A←↑1+P Q       We turn (1+P Q) into an 2-by-longest-path-length array 
                    pads with 0s and our alphabet also uses 0s
                   So we add 1 first to avoid erroneous common ancestor matches
    Common←⊃⍸≠⌿A   We find the length of the common ancestor, Common

         I←⍬{...}P   Now we get the shortest backtracking path from P
    I←⍬{
        Common=≢⍵:⍺        If P is shorter than Common, return our backtrack path
        s←⌽⊢.⊢⌸⍵           Get the indices of the most recent N SW SE
        ts[⊃⍋|Common-s]   and keep the index that is closest to Common
                           and favoring the ancestors to the right of
                           Common in a tiebreaker (which is why we reverse ⊢.⊢⌸⍵)
        (⍺,t)∇t↑⍵          Then repeat this with each new index to backtrack
    }P                     and the path left to backtrack through

    BacktrackP[I]    We get our backtrack directions
    Forward←(⊃⌽I)↓Q   and our forward moves to Q
                      starting from the appropriate ancestor
    (∪¨↓6 2'SSNENWNNSWSE')[Backtrack,Forward]     'S' 'NE' 'NW' 'N' 'SW' 'SE'
}                     and return those directions

Альтернативне рішення з використанням Dyalog Extended та dfns

Якщо ми використовуємо функцію ⎕CY 'dfns'' adic, вона реалізує наше біективне число-базове n (яке було натхненням для версії, яку я використовую) на набагато менше байтів. Перехід на Dyalog Extended також економить досить багато байтів, і ось ми тут. Велика подяка Адаму за його допомогу в цьому. Пропозиції з гольфу вітаються!

Редагувати: -8 байт за рахунок зміни способу Pта Qвизначення. -14 байт за рахунок переходу на Dyalog Extended. -2 завдяки використанню повної програми для видалення дужок dfn {}. +17 байт через виправлення проблеми, коли мій алгоритм виявився невірним, завдяки багатьом завдяки ngn за допомогу у фігурації s[⊃⍋|M-s]розділу. +1 байт для виправлення помилок. -2 байти завдяки Адаму, що переставив визначення I та -1 байт, щоб запам'ятати, щоб розмістити свої гольфи в обох рішеннях . -3 байти завдяки ngn шляхом перестановки генерації кардинальних напрямків, +1 байт від виправлення баггі-гольфу, і -3 байти завдяки ngn шляхом перестановки, як tвизначено (це вже не окрема змінна). -7 байт завдяки ngn шляхом перестановки, як sвизначено.

APL (Dyalog Extended) , 123 115 101 99 116 117 114 109 102 байт

M←⊃⍸≠⌿↑1+P Q←(⍳3)∘⌂adic¨⎕⋄(∪¨↓6 2'SSNENWNNSWSE')[P[I],3+Q↓⍨⊃⌽I←⍬{M≥≢⍵:⍺⋄(⍺∘,∇↑∘⍵){⍵[⊃⍋|M-⍵]}⌽⊢.⊢⌸⍵}P]

Спробуйте в Інтернеті!


Для 66 та 1 це не найкоротший шлях через 0.
Крістіан Сіверс

@ChristianSievers Ви абсолютно праві, і я ще не знаю, як це виправити. Дякую, що повідомив.
Шерлок9
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.