Комбінаторика: обчислити субфакториал


25

У subfactorial або Rencontres номер ( A000166 ) являє собою послідовність чисел , подібних факторним числа , які показують в комбінаториці перестановок. Зокрема, n- й підфактор ! N дає кількість дерангувань набору з n елементів. Дезорганізація - перестановка, в якій жоден елемент не залишається в тому ж положенні. Підфактор можна визначити за допомогою наступного відношення:

!n = (n-1) (!(n-1) + !(n-2))

Насправді, однакове відношення повторення має місце і для факторіалу, але для субфакторалу ми починаємо з:

!0 = 1
!1 = 0

(Для фабрикантів ми мали б, звичайно, 1! = 1. )

Ваше завдання - обчислити ! N , задану n .

Правила

Як і факторний, субфакториал росте дуже швидко. Добре, якщо ваша програма може обробляти лише входи n такі, що ! N може бути представлено типом рідного номера вашої мови. Однак ваш алгоритм повинен теоретично працювати для довільних n . Це означає, що ви можете припустити, що інтегральні результати та проміжне значення можуть бути представлені саме вашою мовою. Зауважте, що це виключає константу e, якщо вона зберігається або обчислюється з обмеженою точністю.

Результат повинен бути точним цілим числом (зокрема, ви не можете зблизити результат науковими позначеннями).

Ви можете написати програму або функцію та використовувати будь-який із стандартних методів отримання вводу та надання виводу.

Ви можете використовувати будь-яку мову програмування , але зауважте, що ці лазівки за замовчуванням заборонені.

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

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

n     !n
0     1
1     0
2     1
3     2
4     9
5     44
6     265
10    1334961
12    176214841
13    2290792932
14    32071101049
20    895014631192902121
21    18795307255050944540
100   34332795984163804765195977526776142032365783805375784983543400282685180793327632432791396429850988990237345920155783984828001486412574060553756854137069878601

Відповіді:


19

Funciton , 336 байт

Кількість байтів передбачає кодування UTF-16 з BOM.

┌─╖┌─╖  ┌─╖ 
│f╟┤♭╟┐┌┤♭╟┐
╘╤╝╘═╝├┘╘═╝├────┐
 │┌─╖ │ ┌┐┌┘╔═╗╓┴╖
 ││f╟─┴┐└┴┼─╢0║║f║
 │╘╤╝  │  │ ╚═╝╙─╜
 │┌┴╖ ┌┴╖┌┴╖ ╔═╗
 ││+╟┐│×╟┤?╟┐║1║
 │╘╤╝│╘╤╝╘╤╝┘╚╤╝
 └─┘ └─┘  └───┘

Це визначає функцію, fяка приймає одне ціле число і виводить інше ціле число під поворотом на 90 градусів вліво. Він працює для довільно великих входів.

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

Враховуючи це Funciton, це навіть досить швидко (n = 20 займає близько 14 секунд на TIO). Основне уповільнення відбувається через подвійну рекурсію, оскільки я не думаю, що інтерпретатор Funciton автоматично запам'ятовує функції.

На жаль, деякі однорозмірні шрифти неправильно виділяють простір та / або вставляють невеликі проміжки між рядками. Ось скріншот коду від TIO у всій його красі:

введіть тут опис зображення

Я думаю, що це можливо ще трохи пограти в гольф, наприклад, змінивши умову >0на <1і замінивши гілки на умовні, щоб я міг повторно використовувати число буквальне, або, можливо, використовуючи зовсім іншу формулу, але я цілком задоволений тим, наскільки він компактний.

Пояснення

Це в основному реалізує рекурсію, задану в виклику, хоча він використовує базовий випадок ! (- 1) =! 0 = 1 . n-1 і n-2 обчислюються за допомогою функції попередника , і проміжний результат n-1 повторно використовується в трьох місцях. Більше цього не багато, тому я просто швидко пройду контрольний потік:

               ─┐
               ╓┴╖
               ║f║
               ╙─╜

Це заголовок функції, який випромінює вхід функції n довгий доданий рядок. Це негайно досягає T-переходу, який просто дублює значення.

        ┌┐┌┘╔═╗
        └┴┼─╢0║
          │ ╚═╝

0Вікно просто числовий літерал. Чотиристоронній перехід обчислює дві функції: шлях, який веде від нижнього обчислює 0 <n , який ми використаємо для визначення базового випадку. Шлях, який йде вліво окремо, обчислює 0 << n (зсув ліворуч), але ми відкидаємо це значення за допомогою конструкції Старкова .

         ┌┴╖ ╔═╗
         ┤?╟┐║1║
         ╘╤╝┘╚╤╝
          └───┘

Ми переводимо це в тристороннє умовне ?. Якщо значення хибне, ми повертаємо постійний результат 1. Правий вільний кінець праворуч ?є вихідним функцією. Я перекручую його на 180 градусів, щоб відносна орієнтація вводу та виходу fбула зручнішою в решті програми.

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

        ┌─╖ 
      ┐┌┤♭╟┐
      ├┘╘═╝
      │
     ─┴┐

В іншій гілці ми спочатку обчислюємо n-1, а потім розділяємо шлях вдвічі, тому отримуємо три копії значення (одна для коефіцієнта повторення, одна для першого підфактора, остання для n-2 ).

┌─╖┌─╖
│f╟┤♭╟
╘╤╝╘═╝
 │┌─╖
 ││f╟
 │╘╤╝
 │┌┴╖
 ││+╟
 │╘╤╝
 └─┘ 

Як я вже говорив, ми знову декрементуємо одну копію з іншою , потім подаємо і n-1 і n-2 рекурсивно, fі, нарешті, додаємо два результати разом у +.

       ┐
       │
      ┌┴╖
     ┐│×╟
     │╘╤╝
     └─┘

Залишилося помножити n-1 на ! (N-1) +! (N-2) .


13

Оазис , 5 байт

Використовує формулу, подану Мартіном. Код:

+n<*X

Розсічена версія:

+n<*

з a(0) = 1і a(1) = 0.

Пояснення a(n) =:

+       # Add the previous two terms, a(n - 1) + a(n - 2).
 n<     # Compute n - 1.
   *    # Multiply the top two elements.

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


Гарний трюк із використанням X:-) BTW, ти це ще реалізував ? Одного дня ми не зможемо піти, просто змінивши початкові значення
Луїс Мендо,

@LuisMendo Так, я! Він використовується як командний прапор ( тут знаходиться посилання на сторінку інформації). Дякую за пропозицію :).
Аднан


7

Желе , 7 байт

R=Œ!Ḅċ0

Такий підхід конструює дезактиви, тому він досить повільний.

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

Як це працює

R=Œ!Ḅċ0  Main link. Argument: n

R        Range; yield [1, ..., n].
  Œ!     Yield all permutations of [1, ..., n].
 =       Perform elementwise comparison of [1, ..., n] and each permutation.
    Ḅ    Unbinary; convert each result from base 2 to integer. This yields 0 for
         derangements, a positive value otherwise.
     ċ0  Count the number of zeroes.

7

Брахілог (2), 11 байт

⟦₁{p:?\≠ᵐ}ᶜ

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

Пояснення

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

⟦₁{p:?\≠ᵐ}ᶜ
⟦₁           Start with a list of {the input} distinct elements
  {      }ᶜ  Then count the number of ways to
   p         permute that list
      \      such that taking corresponding elements
    :?       in {the permutation} and the list of distinct elements
       ≠     gives different elements
        ᵐ    at every position

5

Мови із вбудованими рішеннями

Після пропозиції xnor це відповідь CW, в якій тривіальні рішення, засновані на єдиному вбудованому для обчислення підфактора або генерування всіх деранжувань, повинні бути відредаговані.

Математика, 12 байт

Subfactorial

Зітхання Mathematica ...
epicbob57

5

Python 3 , 35 32 байти

f=lambda n:n<1or(-1)**n+n*f(n-1)

Для цього використовується відношення рецидивів ! N = n! (N-1) + (-1) n з відповіді Хаскеля @ Лайконі , з базовим випадком ! 0 = 1 .

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


Я думаю , ви також можете використовувати інші рівняння , наведені тут , що дозволить заощадити два байта: f=lambda n:n<1or n*f(n-1)+(-1)**n.
Аднан

1
Три байти з невеликим упорядкуванням. ;)
Денніс

1
Найцікавішою частиною цього повторення є те, що якщо ти повернеш базовий корпус назад n=-1, це зовсім не має значення, яке значення ти використовуєш. Це може бути корисно для деяких мов (наприклад, у Mathematica ви фактично можете залишити його невизначеним, якщо це зберегло байти).
Мартін Ендер

5

М , 9 байт

o2!÷Øe+.Ḟ

Як ви бачите, видаливши , M використовує символічну математику, тому проблем з точністю не виникне.

Спробуйте в Інтернеті!Не найкоротше рішення, яке було розміщено, але швидке .

Як це працює

o2!÷Øe+.Ḟ  Main link. Argument: n

o2         Replace input 0 with 2, as the following formula fails for 0.
  !        Compute the factorial of n or 2.
   ֯e     Divide the result by e, Euler's natural number.
      +.   Add 1/2 to the result.
        Ḟ  Floor; round down to the nearest integer.

5

MATL , 9 8 байт

:tY@-!As

Аналогічно до відповіді Jelly @Dennis , це фактично створює перестановки і підраховує, скільки з них є дерангуваннями; тому це повільно.

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

:     % Input n implicitly: Push [1 2 ... n]
t     % Duplicate 
Y@    % Matrix of all permutations, each on a row
-     % Element-wise subtract. A zero in a row means that row is not a derangement
!     % Transpose
A     % True for columns that don't contain zeros
s     % Sum. Implicitly display

3

Математика , 21 байт

Round@If[#>0,#!/E,1]&

Я дуже новачок у цьому і поняття не маю, що я роблю ...

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


1
Дві альтернативи в одному байті підрахунку: Round[(#/. 0->2)!/E]&і ±0=1;±n_:=Round[n!/E](хоча я не знаю, чи підтримує Mathics однобайтові кодування для вихідних файлів, як це робить Mathematica).
Мартін Ендер

Перший працює добре (я думаю, я знаю, що це робить), але, здається, математика не підтримує ±другу. Він би працював f, але ціною у два байти.
Денніс

Інші в тому ж байті розраховувати: Round[#!/E]+1-Sign@#&. Дратівливі початкові значення ...!
Грег Мартін


3

CJam (10 байт)

1qi{~*)}/z

Інтернет демо .

Для цього використовується рецидив !n = n !(n-1) + (-1)^n, з якого я був отриманийn! / e а потім виявив, вже в OEIS.

Розсічення

Цикл обчислюється (-1)^n !n, тому нам потрібно взяти абсолютне значення в кінці:

1     e# Push !0 to the stack
qi{   e# Read an integer n and loop from 0 to n-1
  ~   e#   Bitwise not takes i to -(i+1), so we can effectively loop from 1 to n
  *   e#   Multiply
  )   e#   Increment
}/
z     e# Take the absolute value


2

MATLAB, 33 байти

@(n)(-1)^n*hypergeom([1 -n],[],1)

Функція Anonymousmpus, яка використовує формулу в Розділі 3 Дерангування та програми Мехді Хассані.

Приклад використання:

>> @(n)(-1)^n*hypergeom([1 -n],[],1)
ans = 
    @(n)(-1)^n*hypergeom([1,-n],[],1)
>> ans(6)
ans =
   265

2

JavaScript (ES6), 26 байт

f=n=>!n||n*f(n-1)-(~n%2|1)

Використовує відношення рецидиву з відповіді @ Лайконі. У ES7 ви можете зберегти байт, використовуючи +(-1)**nзамість -(~n%2|1).


2

PostScript, 81 76 69 байт

Ось реалізація обох формул.

n * f (n-1) + (- 1) ^ n

/ f {dup 0 eq {pop 1} {dup dup 1 sub f mul exch 2 mod 2 mul 1 sub sub} ifelse} def

/f{dup 0 eq{pop 1}{dup dup 1 sub f mul -1 3 2 roll exp add}ifelse}def

Ця версія видає float. Якщо необхідно вивести ціле число:

/f{dup 0 eq{pop 1}{dup dup 1 sub f mul -1 3 2 roll exp cvi add}ifelse}def

яка важить в 73 байти.

Інша формула трохи довша: 81 байт.

(n-1) * (f (n-1) + f (n-2))

/f{dup 1 le{1 exch sub}{1 sub dup f exch dup 1 sub f 3 -1 roll add mul}ifelse}def

Ці функції отримують свій аргумент із стека та залишають результат у стеці.

Ви можете протестувати функції як у файлі, так і в інтерактивному записі PostScript (наприклад, GhostScript) за допомогою

0 1 12{/i exch def [i i f] ==}for

вихід

[0 1]
[1 0.0]
[2 1.0]
[3 2.0]
[4 9.0]
[5 44.0]
[6 265.0]
[7 1854.0]
[8 14833.0]
[9 133496.0]
[10 1334961.0]
[11 14684570.0]
[12 176214848.0]

Ось повний файл PostScript, який надає вихід на екран або сторінку принтера. (Коментарі в PostScript починаються з %).

%!PS-Adobe-3.0

% (n-1)*(f(n-1)+f(n-2))
% /f{dup 1 le{1 exch sub}{1 sub dup f exch dup 1 sub f 3 -1 roll add mul}ifelse}def

% n*f(n-1)+(-1)^n
/f{dup 0 eq{pop 1}{dup dup 1 sub f mul -1 3 2 roll exp add}ifelse}def

% 0 1 12{/i exch def [i i f] ==}for

/FS 16 def              %font size
/LM 5 def               %left margin
/numst 12 string def    %numeric string buffer

/Newline{currentpoint exch pop FS sub LM exch moveto}def
/Courier findfont FS scalefont setfont
LM 700 moveto

(Subfactorials) Newline
0 1 12{
    dup numst cvs show (: ) show f numst cvs show Newline
}for
showpage
quit

1
-1 3 2 roll expсправедливо трохи коротше, ніж exch 2 mod 2 mul 1 sub.
Пітер Тейлор

@PeterTaylor Так і є! :) Я забув про exp: oops: Однак, він повертає float, і я думаю, мені потрібно вивести ціле число, щоб відповідати питанню.
PM 2Ring

1
Я думаю, що ти все ще можеш пограбувати у cviта зробити економію. (Примітка: неперевірений, але після читання документа я думаю, що це має працювати).
Пітер Тейлор

@PeterTaylor Так, cviпрацює, і він все ще коротший, ніж моя попередня версія.
PM 2Ring

1

PHP, 69 байт

function f($i){return$i>1?$i*f($i-1)+(-1)**$i:1-$i;}echo f($argv[1]);

використовувати цей спосіб a(n) = n*a(n-1) + (-1)^n


1
Потрібно лише надати функцію, а не повну програму, щоб ви могли скинути останні 17 символів. Подальша економія не є спеціальним кожухом 1. Я думаю, що дві економії знижують її до 47 байт.
Пітер Тейлор

1

PHP, 50 44

for(;$i++<$argn;)$n=++$n*$i-$i%2*2;echo$n+1;

Бігайте з echo <n> | php -nR '<code>

Краса Росії a(n) = n*a(n-1) + (-1)^nполягає в тому, що це залежить лише від попереднього значення. Це дозволяє реалізовувати його ітеративно, а не рекурсивно. Це економить тривале оголошення функції .

-6 байт від @Titus. Спасибі !


-1 байт: $i++<$argv[1]. -2 байт: for(;$i++<$argv[1];)$n=++$n*$i-$i%2*2;echo$n+1;. (-3 байти з -Rі $argn.)
Тит

@Titus комусь набридло? : D Ви б не проти подати мені приклад -Rі $argn?
Крістоф

1
Не нудьгує, але прагне гольфу. Дивіться php.net/manual/de/features.commandline.options.php: echo <input> | php -nR '<код>'. приклад: codegolf.stackexchange.com/a/113046
Тит

1
@Titus я правильно зрозумів? ;-)
Крістоф

0

Математика, 40 байт

±0=1;±1=0;±n_:=(n-1)(±(n-1)+±(n-2))

Котрий входить у 31 байт під кодування ISO 8859-1 за замовчуванням.


0

C, 34 байти

a(n){return n?n*a(n-1)-n%2*2+1:1;}

Пояснення:

a(n){                            } define a function called a of n
     return                     ;  make the function evaluate to...
            n?                :1   set the base case of 1 when n is 0
              n*a(n-1)             first half of the formula on the page
                      -n%2*2+1     (-1)**n

0

R, 47 байт

n=scan();`if`(!n,1,floor(gamma(n+1)/exp(1)+.5))

Використовується та ж формула, що і у відповіді Мего .

Альтернативний метод, 52 байти за допомогою PerMallowsбібліотеки

n=scan();`if`(!n,1,PerMallows::count.perms(n,n,'h'))

0

Власне , 18 байт

;!@ur⌠;!@0Dⁿ/⌡MΣ*≈

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

Пояснення:

;!@ur⌠;!@0Dⁿ/⌡MΣ*≈
;                   duplicate input
 !                  n!
  @ur               range(0, n+1) (yields [0, n])
     ⌠;!@0Dⁿ/⌡M     for each i in range:
      ;               duplicate i
       !              i!
        @0Dⁿ          (-1)**i
            /         (-1)**i/i!
               Σ    sum
                *   multiply sum by n!
                 ≈  floor into int

12-байтна версія, яка працювала, якби насправді була більш точна:

;!╠@/1½+L@Y+

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

На відміну від усіх інших відповідей (станом на публікацію), це рішення не використовує ані рекурсивної формули, ані формули підсумовування. Натомість використовується наступна формула:

формула розчарування

Насправді цю формулу можна легко реалізувати:

!╠@/1½+L
!         n!
 ╠        e
  @/      divide n! by e
    1½+   add 0.5
       L  floor

Тепер єдина проблема полягає в тому, що формула має лише позитив n. Якщо ви намагаєтесь використовувати n = 0, формула невірно виходить 0. Однак це легко виправити: застосувавши булеве заперечення до введення та додавши, що до виходу формули задається правильний вихід для всіх негативних n. Таким чином, програма з цією корекцією:

;!╠@/1½+L@Y+
;             duplicate input
 !            n!
  ╠           e
   @/         divide n! by e
     1½+      add 0.5
        L     floor
         @Y   boolean negate the other copy of the input (1 if n == 0 else 0)
           +  add

Продовжує давати негативні відповіді на мене ...
Leaky Nun

@LeakyNun Це через межі точності. Для великих входів (навколо n = 100), (-1)**n/n!їх не можна представити двояковими плавцями IEEE 754. Це прийнятно відповідно до виклику.
Мего

Навіть для n=4
Leaky Nun

@LeakyNun О. Я не знаю, чому я використовував флоральний поділ. Виправити це зараз.
Мего



0

Аліса , 20 18 байт

1/o
k\i@/&wq*eqE+]

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

Пояснення

При цьому використовується рекурсія від Laikoni відповідають , ! П = п! (П-1) + (-1) п . Подібно до відповіді Funciton, я використовую базовий випадок ! (- 1) = 1 . Поставимо той 1 на стек с 1.. Потім це ...

.../o
...\i@/...

... це лише звичайна рамка десяткового вводу / виводу. Основний код є насправді

&wq*eqE+]k

Зломаний:

&w    Push the current IP address N times to the return address stack, which
      effectively begins a loop which will be executed N+1 times.
  q     Push the position of the tape head, which we're abusing as the
        iterator variable n.
  *     Multiply !(n-1) by n.
  e     Push -1.
  q     Retrieve n again.
  E     Raise -1 to the nth power.
  +     Add it to n*!(n-1).
  ]     Move the tape head to the right.
k     Jump back to the w, as long as there is still a copy of the return
      address on the return address stack. Otherwise, do nothing and exit
      the loop.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.