Дивовижний трюк#11320

Дивовижний трюк (Розбір)

Після перетасовки -та карта зліва — це карта a[p[q[i]]]. Фокус вдається, якщо для всіх виконується рівність:

a[p[q[i]]] = i.

Позначимо через p \circ q композицію перестановок: (p \circ q)[i] = p[q[i]], тобто спочатку застосовується , а потім . Нехай — тотожна перестановка. Тоді умову фокуса можна записати як a \circ p \circ q = I.

Застосуємо обернену перестановку a^{-1} до обох частин рівності зліва, а потім p^{-1}:

p \circ q = a^{-1}, \qquad q = p^{-1} \circ a^{-1}.

Таким чином, перестановку можна обрати довільно, а потім визначається однозначно: q[i] = p^{-1}[a^{-1}[i]]. Перестановку без нерухомих точок називають безладом (derangement). Задача зводиться до пошуку такого безладу , що перестановка q = p^{-1} \circ a^{-1} також є безладом.

Випадковий вибір. Перебрати всі перестановок неможливо, тому ми обираємо випадково:

Ми робимо не більше спроб. Якщо жодна з них не дає відповіді, виводимо "Impossible".

Чому це працює. Відповіді не існує лише у кількох випадках для малих :

Для n \ge 4 відповідь завжди існує. Більш того, як показує повний перебір для малих , частка придатних перестановок серед усіх перестановок становить щонайменше (мінімум досягається при n = 4), а зі зростанням вона наближається до 1/e^2 \approx 0.135. Отже, ймовірність не знайти відповідь за спроб не перевищує (11/12)^{1000} < 10^{-37}, що практично дорівнює нулю. Якщо відповіді не існує, жодна зі спроб не вдасться, і програма коректно виведе "Impossible".

Приклад

Розглянемо четвертий тест: a = (5, 1, 4, 2, 3).

Знайдемо обернену перестановку: якщо a[i] = x, то a^{-1}[x] = i. Оскільки a[1] = 5, a[2] = 1, a[3] = 4, a[4] = 2, a[5] = 3, отримуємо a^{-1} = (2, 4, 5, 3, 1). На малюнку зліва показана перестановка , а справа — обернена до неї: всі стрілки розвернуті.

Згенеруємо випадкову перестановку без нерухомих точок. Нехай, наприклад, p = (4, 1, 2, 5, 3). Обернена до неї p^{-1} = (2, 3, 5, 1, 4).

Обчислимо q[i] = p^{-1}[a^{-1}[i]]:

q[1] = p^{-1}[2] = 3, \quad q[2] = p^{-1}[4] = 1, \quad q[3] = p^{-1}[5] = 4, \\ q[4] = p^{-1}[3] = 5, \quad q[5] = p^{-1}[1] = 2

Отримуємо q = (3, 1, 4, 5, 2). Перестановка не має нерухомих точок, тому пара — відповідь. Вона збігається з відповіддю з прикладу.

Переконаємося, що фокус вдається, тобто a[p[q[i]]] = i:

a[p[3]] = a[2] = 1, \quad a[p[1]] = a[4] = 2, \quad a[p[4]] = a[5] = 3, \\ a[p[5]] = a[3] = 4, \quad a[p[2]] = a[1] = 5

У першому тесті n = 2 та a = (2, 1), тому a^{-1} = (2, 1). Єдина перестановка довжини без нерухомих точок — це p = (2, 1), і p^{-1} = (2, 1). Тоді q = p^{-1} \circ a^{-1} = (1, 2) — тотожна перестановка, всі точки якої нерухомі. Відповідь — "Impossible".

Реалізація алгоритму

Функція print виводить елементи масиву , починаючи з першого.

void print(vector<int>& a)
{
  for (int i = 1; i < a.size(); i++)
    printf("%d ", a[i]);
  printf("\n");
}

Функція is_valid перевіряє, чи містить перестановка нерухомі точки.

bool is_valid(vector<int>& v)
{
  for (int i = 1; i < v.size(); i++)
    if (v[i] == i) return false;
  return true;
}

Оголосимо масиви.

vector<int> a, p, p1, q;

Основна частина програми. Зчитуємо вхідні дані.

scanf("%d", &tests);
while (tests--)
{
  scanf("%d", &n);

Ініціалізуємо масиви.

  a.resize(n + 1);
  p1.resize(n + 1);
  p.resize(n + 1);
  q.resize(n + 1);

Зчитуємо вхідну перестановку та одразу зберігаємо обернену до неї у масиві . Тобто масив містить перестановку a^{-1}, де — вхідна перестановка.

  for (i = 1; i <= n; i++)
  {
    scanf("%d", &x);
    a[x] = i;
  }

mt19937 — один зі стандартних генераторів псевдовипадкових чисел у C++ (вихор Мерсенна з періодом 19937). Він дуже швидкий і має хороші статистичні властивості, що робить його відмінним вибором для більшості задач, що потребують генерації випадкових чисел.

  mt19937 gen(random_device{}());

У змінній позначатимемо, чи знайдено розв'язок для поточного тесту.

  found = false;

Виконуємо ітерацій.

  for (it = 0; it < 1000; it++)
  {

Генеруємо випадкову перестановку. Для цього заповнюємо масив числами 0, 1, 2, ..., і використовуємо функцію shuffle для перемішування елементів, починаючи з першого (ненульового) елемента. Ми працюємо з перестановками від до .

    iota(p.begin(), p.end(), 0);
    shuffle(p.begin() + 1, p.end(), gen);

Перевіряємо, чи є перестановка допустимою (тобто чи не містить вона нерухомих точок).

    if (!is_valid(p)) continue;

Обчислюємо перестановку p_1 = p^{-1}.

    for (i = 1; i <= n; i++)
      p1[p[i]] = i;

Обчислюємо перестановку q = p_1 ° a^{-1} = p^{-1} ° a^{-1}.

    for (i = 1; i <= n; i++)
      q[i] = p1[a[i]];

Якщо перестановка допустима, виводимо результат.

    if (is_valid(q))
    {
      puts("Possible");
      print(p);
      print(q);
      found = true;
      break;
    }
  }

Якщо після ітерацій розв'язок не знайдено, то відповіді не існує.

  if (!found) puts("Impossible");
}