Спочатку у нас є масив \{1, 2, ..., n\}. Далі виконується процедура тасування. Нам дана фінальна перестановка . Потрібно знайти ймовірність того, що саме її ми отримаємо після виконання процедури.
Шукана ймовірність дорівнює:
На кожному кроці тасування вибирається одне число . Кількість варіантів вибору числа дорівнює . Кількість кроків тасування теж . Отже існує n^n послідовностей виборів. Відзначимо, що різні послідовності виборів можуть призвести до однакової підсумкової перестановки. Тому що n^n набагато більше, ніж .
Занесемо в масив послідовність \{1, 2, ..., n\}. Будемо перебирати всі можливі варіанти вибору (r_1, r_2, ..., r_n), моделюючи в всі можливі процеси тасування (1 \le r_i \le n). Зауважимо, що фіксована послідовність чисел r_1, r_2, ..., r_n однозначно визначає, які саме обміни будуть виконані на кожному кроці, а отже і підсумковий масив після завершення процедури. Якщо після застосування всіх обмінів ми отримали масив , вважаємо спробу успішною. Нехай таких спроб вийшло . Тоді шукана ймовірність дорівнює
Реалізуємо функцію shuffle, яка здійснює рекурсивний перебір усіх можливих продовжень процесу тасування і підраховує, скільки з них призводять до цільової перестановки .
Функція має один параметр — номер поточної ітерації (стартує з ). Це означає, що перші обмінів уже виконані, а масив зберігає стан, отриманий після цих операцій.
Щоб вкластися в обмеження за часом, застосуємо наступне відсічення. Нехай виконується ітерація з номером , і поточний масив відрізняється від цільового масиву у позиціях. Залишилося виконати n - pos обмінів. Зауважимо, що за один обмін можна поставити на свої місця не більше двох елементів. Отже, сумарно за решту кроків можна виправити максимум 2 \cdot (n - pos) позицій.
Якщо ж diff > 2 \cdot (n - pos), то отримати з масив вже неможливо. У цьому випадку подальший перебір не має сенсу, і поточну гілку рекурсії можна негайно перервати.
Реалізація алгоритму
Оголосимо робочі масиви:
— задана перестановка ;
— поточна перестановка при переборі;
int a[10], cur[10];Функція shuffle рекурсивно перебирає всі можливі продовження процесу тасування, рахуючи, скільки з них призведуть до цільової перестановки .
void shuffle(int pos)
{Виконані всі операцій. Залишається перевірити — чи збігається отриманий масив із необхідним . Якщо збігається, значить знайдено ще один успішний сценарій. У цьому випадку збільшуємо .
if (pos == n)
{
for (int i = 0; i < n; i++)
if (cur[i] != a[i]) return;
countGood++;
return;
}У змінній підраховуємо, у скількох позиціях поточний масив відрізняється від потрібного .
int diff = 0;
for (int i = 0; i < n; i++)
if (cur[i] != a[i]) diff++;На даний момент нам залишилося виконати n - pos кроків. За один обмін можна виправити максимум дві неправильні позиції. Якщо вже на даний момент відмінностей більше, то виконувати далі рекурсію немає сенсу.
Ми просто припиняємо розглядати цю гілку перебору.
if (diff > 2 * (n - pos)) return;Перебираємо всі можливі значення .
for (int r = 0; r < n; r++)
{
swap(cur[pos], cur[r]);
shuffle(pos + 1);
swap(cur[pos], cur[r]);
}
}Основна частина програми. Читаємо число — розмір масиву .
while (scanf("%d", &n) == 1)
{Читаємо вхідний масив . Моделювання завжди починається з масиву \{1, 2, ..., n\}. Його і заносимо в .
for (int i = 0; i < n; i++)
{
scanf("%d", &a[i]);
cur[i] = i + 1;
}Запускаємо перебір. У змінній підраховуємо, скільки послідовностей випадкових виборів призведуть до масиву .
countGood = 0;
shuffle(0);Обчислюємо і виводимо відповідь.
res = countGood / pow(n, n);
printf("%.6lf\n", res);
}