Оцінка Олімпійського звичайного розмахування лози Тарзана


32

Олімпійські виноградники-виноградники виконують свою процедуру на стандартних деревах. Зокрема, у стандартного дерева nє вершини 0вгору n-1та по краях, що з'єднують кожну ненульову вершину aз вершиною n % aпід нею. Так, наприклад, Стандартне дерево 5 виглядає так:

3
|
2   4
 \ /
  1
  |
  0

оскільки залишок, коли 5 ділиться на 3, дорівнює 2, решта, коли 5 ділиться на 2 або 4, дорівнює 1, а залишок, коли 5 ділиться на 1, дорівнює 0.

Цього року Тарзан буде захищати своє золото новими процедурами, кожна з яких починається у вершині n - 1, коливається до вершини n - 2, продовжує вершину n - 3тощо, поки нарешті він не зійде на вершину 0.

Оцінка за звичайну програму - це сума балів за кожну гойдалку (включаючи демонтаж), а оцінка для гойдалки - це відстань у дереві між його початковою та кінцевою точками. Таким чином, звичайний Тарзан на стандартному дереві 5 має оцінку 6:

  • свінг від 4до 3оцінки трьох точок (вниз, вгору, вгору),
  • свінг від 3до 2одне очка (вниз),
  • свінг від 2до 1десятків в одній точці (вниз), і
  • від'єдналися від 1до 0одне очка (вниз).

Напишіть програму або функцію, яка за допомогою додатного цілого числа nобчислює оцінку рутинної програми Тарзана на Стандартному дереві n. Зразки входів і виходів:

 1 ->  0
 2 ->  1
 3 ->  2
 4 ->  6
 5 ->  6
 6 -> 12
 7 -> 12
 8 -> 18
 9 -> 22
10 -> 32
11 -> 24
12 -> 34
13 -> 34
14 -> 36
15 -> 44
16 -> 58
17 -> 50
18 -> 64
19 -> 60
20 -> 66
21 -> 78
22 -> 88
23 -> 68
24 -> 82

Правила та підрахунок коду, як звичайно, для .


9
Мені не вдалося знайти цю послідовність в ОЕІС. Приємне запитання.
Leaky Nun

8
Відмінна специфікація!
xnor

1
@LeakyNun Це слід додати, хоча. Це дуже оригінальна послідовність! (Навіть без попередньої історії)
DanTheMan

Відповіді:


12

C, 98 97 байт

F(i){int c[i],t=i-2,n=0,p;for(;++n<i;)for(p=c[n]=n;p=i%p;c[p]=n)t+=c[p]<n-1;return i>2?t*2:i-1;}

При цьому обчислюється відстань між кожною парою точок за такою формулою:

  • Додайте відстань від кореня до вузла A
  • Додайте відстань від кореня до вузла B
  • Відніміть 2 * довжину загального кореня A і B

Це має ту перевагу, що при застосуванні до всіх пар це те саме, що:

  • Додайте 2 * відстань від кореня до кожного вузла
  • Відніміть 2 * довжину загального кореня кожної пари вузлів
  • Відніміть відстань від кореня до першого вузла
  • Відніміть відстань від кореня до останнього вузла

Щоб зробити логіку більш простою, ми припускаємо, що ми переходимо від вузла 0 до вузла n-1, а не від n-1 до 0, як зазначено в запитанні. Відстань від кореневого вузла до вузла 0 очевидно 0 (вони однакові). І ми можемо бачити, що для (більшості) дерев відстань від останнього вузла до кореня становить 2:

                    n+1 % n = 1  for all n > 1
and:                  n % 1 = 0  for all n >= 0
therefore:  n % (n % (n-1)) = 0  for all n > 2

Це означає, що у нас є деякі особливі випадки (n <2), але ми можемо їх пояснити досить легко.

Зламатися:

F(i){                               // Types default to int
    int c[i],                       // Buffer for storing paths
        t=i-2,                      // Running total score
        n=0,                        // Loop index
        p;                          // Inner loop variable
    for(;++n<i;)                    // Loop through all node pairs (n-1, n)
        for(p=c[n]=n;p=i%p;c[p]=n)  //  Recurse from current node (n) to root
            t+=c[p]<n-1;            //   Increase total unless this is a common
                                    //   node with the previous path
    return i>2?   :i-1;             // Account for special cases at 1 and 2
               t*2                  // For non-special cases, multiply total by 2
}

Дякуємо @feersum за 1 байт


Бонус: Дерева!

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

6:

5 4  
| |  
1 2 3
 \|/ 
  0  

8:

  5      
  |      
7 3   6  
|  \ /   
1   2   4
'--\|/--'
    0    

13:

   08              
    |              
11 05   10 09 07   
 |   \ /    |  |   
02   03    04 06 12
 '-----\  /---'--' 
        01         
         |         
        00         

19:

   12                       
    |                       
   07   14                  
     \ /                    
     05    15 11            
       \  /    |            
17      04    08 16 13 10   
 |       '-\  /--'   |  |   
02          03      06 09 18
 '---------\ |/-----'--'--' 
            01              
             |              
            00              

49:

                         31                                                    
                          |                                                    
           30            18   36                                               
            |              \ /                                                 
           19   38 27      13    39 29    32                                   
             \ /    |        \  /    |     |                                   
   26        11    22 44      10    20 40 17   34                              
    |         '-\  /--'        '-\  /--'    \ /                                
47 23   46       05               09        15    45 43 41 37 33 25    35 28   
 |   \ /          '--------------\ |/-------'-----'   |  |  |  |  |     |  |   
02   03                           04                 06 08 12 16 24 48 14 21 42
 '----'--------------------------\ |/----------------'--'--'--'--'--'    \ |/  
                                  01                                      07   
                                   '-----------------\  /-----------------'    
                                                      00                       

У зворотній заяві є кілька зайвих дужок.
feersum

@feersum d'oh! Вони не завжди були зайвими, але тоді я змінив особливу справу. Спасибі!
Дейв

3
Любіть візуалізації!
Едвард

7

Python 2, 85 байт

def f(a,i=1):h=lambda n:n and{n}|h(a%n)or{0};return i<a and len(h(i)^h(i-1))+f(a,i+1)

7

Perl, 65 59 55 54 байт

Включає +2 для -ap

Запустити з розміром дерева на STDIN:

for i in `seq 24`; do echo -n "$i: "; vines.pl <<< $i; echo; done

vines.pl:

#!/usr/bin/perl -ap
$_=map{${"-@F"%$_}|=$_=$$_|$"x$p++.1;/.\b/g}1-$_..-1

Пояснення

Якщо переписати дерево

3
|
2   4
 \ /
  1
  |
  0

до того, де кожен вузол містить набір усіх своїх предків і себе:

 {3}
  |
{2,3}   {4}
   \    /
    \  /
  {1,2,3,4}
      |
 {0,1,2,3,4}

Тоді ми можемо описати, наприклад, всі вузли шлях від 4 до 3 як:

  • Усі вузли, які містять 3, але не 4 (йде від 3)
  • Усі вузли, що містять 4, але не 3 (йде від 4)
  • Найвищий вузол, який містить 3 і 4 (з'єднання)

Кількість ребер на один менше, ніж кількість вузлів, тому ми можемо використовувати це для ігнорування точки з'єднання, тому кількість ребер на шляху від 4 до 3 дорівнює 3, оскільки:

  • Кількість вузлів, які містять 3, але не 4: 2 вузли
  • Кількість вузлів, які містять 4, але не 3: 1 вузол

Зауважте, що це також працює для шляху, який безпосередньо спускається до своєї цілі, наприклад, для шляху від 3 до 2 кількість ребер дорівнює 1, оскільки:

  • Кількість вузлів, які містять 2, але не 3: 0 вузли
  • Кількість вузлів, які містять 3, але не 2: 1 вузол

Тоді ми можемо підсумовувати всі ці комбінації.

Якщо ви замість цього подивитесь лише на вузол, наприклад, вузол 2 із набором предків {2,3}. Цей вузол збирається зробити один раз при обробці шляху, 2 to 1оскільки він містить 2, але не 1. Він нічого не зробить для шляху, 3 to 2оскільки у нього є і 2, і 3, але він зробить один раз при обробці шляху, 4 to 3оскільки у нього є 3, але немає 4. Загалом, число у наборі предків вузла вносить по одному для кожного сусіда (одного нижчого від вищого), якого немає в наборі. За винятком максимального елемента (4 у цьому випадку), який сприяє лише низькому сусіду 3, оскільки шляху немає5 to 4. Simula 0 є одностороннім, але оскільки 0 завжди знаходиться в корені дерева і містить усі числа (це кінцеве з'єднання, і ми не рахуємо приєднання), жодного внеску з 0 не існує, тому найпростіше просто залишити вузол 0 взагалі.

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

Щоб легко обробити сусідів, я буду представляти набори предків як рядок пробілів і 1, де кожен 1 у положенні p являє собою, що n-1-p є предком. Так, наприклад, у нашому випадку n=51 у положенні 0 вказує, що 4 є предком. Я залишу пробіли. Отже, власне зображення дерева, яке я буду буду:

" 1"
  |
" 11"   "1"
   \    /
    \  /
   "1111"

Зауважте, що я залишив вузол 0, який буде представлений через те, "11111"що я ігнорую вузол 0 (він ніколи не сприяє).

Предки без нижнього сусіда тепер представлені кінцем послідовності 1-х. Предки, які не мають вищого сусіда, тепер представлені початком послідовності 1, але ми повинні ігнорувати будь-який початок послідовності на початку рядка, оскільки це буде представляти шлях, 5 to 4який не існує. Ця комбінація точно відповідає регексу /.\b/.

Побудова рядків предків виконується обробкою всіх вузлів у порядку, n-1 .. 1і встановлюють 1 у положенні для самого вузла і роблять "чи" у нащадка.

З усім, що програма досить зрозуміла:

-ap                                                  read STDIN into $_ and @F

   map{                                    }1-$_..-1 Process from n-1 to 1,
                                                     but use the negative
                                                     values so we can use a
                                                     perl sequence.
                                                     I will keep the current
                                                     ancestor for node $i in
                                                     global ${-$i} (another
                                                     reason to use negative
                                                     values since $1, $2 etc.
                                                     are read-only
                       $$_|$"x$p++.1                 "Or" the current node
                                                     position into its ancestor
                                                     accumulator
                    $_=                              Assign the ancestor string
                                                     to $_. This will overwrite
                                                     the current counter value
                                                     but that has no influence
                                                     on the following counter
                                                     values
       ${"-@F"%$_}|=                                 Merge the current node
                                                     ancestor string into the
                                                     successor
                                                     Notice that because this
                                                     is an |= the index
                                                     calculation was done
                                                     before the assignment
                                                     to $_ so $_ is still -i.
                                                     -n % -i = - (n % i), so
                                                     this is indeed the proper
                                                     index
                                     /.\b/g          As explained above this
                                                     gives the list of missing
                                                     higher and lower neighbours
                                                     but skips the start
$_=                                                  A map in scalar context
                                                     counts the number of
                                                     elements, so this assigns
                                                     the grand total to $_.
                                                     The -p implicitly prints

Зауважте, що заміна /.\b/на /\b/вирішує цю проблему з версією цієї проблеми, коли тарзан також стоїть на шляху0 to n-1

Деякі приклади того, як виглядають рядки предків (наводяться в порядку n-1 .. 1):

n=23:
1
 1
  1
   1
    1
     1
      1
       1
        1
         1
          1
          11
         1  1
        1    1
       1      1
      11      11
     1          1
    11  1    1  11
   1              1
  1111  11  11  1111
 111111111  111111111
1111111111111111111111
edges=68

n=24:
1
 1
  1
   1
    1
     1
      1
       1
        1
         1
          1
           1
          1 1
         1   1
        1     1
       1       1
      1         1
     1  1     1  1
    1             1
   11    1   1    11
  1   1         1   1
 1        1 1        1
1                     1
edges=82

Ну, вибачте, що я не зрозумів, що ваша редакція мала лише кілька секунд. У будь-якому випадку, дуже акуратний підхід і пояснення!
FryAmTheEggman

@FryAmTheEggman Немає проблем, ми просто виправляли ту саму проблему з макетом. У будь-якому випадку, так, я цілком задоволений тим, як усі твори зібралися в цій програмі. Наразі я не бачу жиру для зрізання ..
Тон Євангелія

3

Математика, 113 103 102 байт

(r=Range[a=#-1];Length@Flatten[FindShortestPath[Graph[Thread[r<->Mod[a+1,r]]],#,#2]&@@{#,#-1}&/@r]-a)&

-10 байт завдяки @feersum; -1 байт завдяки @MartinEnder

Далі набагато швидше (але довше, на жаль, у 158 байт ):

(a=#;If[a<4,Part[-{1,1,1,-6},a],If[EvenQ@a,-2,1]]+a+4Total[Length@Complement[#,#2]&@@#&/@Partition[NestWhileList[Mod[a,#]&,#,#!=0&]&/@Range@Floor[a/2],2,1]])&

Я вважаю, ви можете призначити речі без використання With. Крім того, схоже, щоразу, коли Rangeвикористовується, aє аргументом, щоб його можна було врахувати.
feersum

1
r=Range[a=#-1]зберігає байт.
Мартін Ендер

2

J, 37 байт

[:+/2(-.+&#-.~)/\|:@(]|~^:(<@>:@[)i.)

Використання:

   f=.[:+/2(-.+&#-.~)/\|:@(]|~^:(<@>:@[)i.)
   f 10
32
   f every 1+i.20
0 1 2 6 6 12 12 18 22 32 24 34 34 36 44 58 50 64 60 66

Спробуйте його онлайн тут.


Мені було б цікаво побачити інформацію про те, як це працює. Також здається, що служба tryj.tk зламана ("не вдалося прочитати локальну сховище ..." та "$ (...) .термінал не є функцією")
Дейв,

@Dave цей сайт не працює для мене також на Chrome, але працює, якщо я спробую використовувати IE або Edge, проте я рекомендую встановити J ( посилання ), якщо вас це цікавить!
милі

@miles Дивно, для мене це працює для всіх браузерів (FF, Chrome, IE).
randomra

Це працювало для мене за допомогою Chrome, але він перестав працювати кілька місяців тому і почав відповідати аналогічним повідомленням про помилку до Дейва
миль

@Edward Зробиться, коли знайду деякий час.
randomra

1

JavaScript (ES6), 118 116 байт

n=>[...Array(n)].map(g=(_,i)=>i?[...g(_,n%i),i]:[],r=0).reduce(g=(x,y,i)=>x.map(e=>r+=!y.includes(e))&&i?g(y,x):x)|r

Відсутність функції різниці заданих дійсно боляче, але деякі творчі рекурсії трохи зменшують кількість байтів. Редагувати: збережено 2 байти, видаливши непотрібний параметр.

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