Dopo il mescolamento, la -esima carta da sinistra è la carta a[p[q[i]]]. Il trucco riesce se per ogni vale la seguente uguaglianza:
Denotiamo con p \circ q la composizione di permutazioni: (p \circ q)[i] = p[q[i]], cioè prima si applica e poi . Sia la permutazione identica. Allora la condizione del trucco può essere scritta come a \circ p \circ q = I.
Applichiamo la permutazione inversa a^{-1} a entrambi i membri dell'uguaglianza a sinistra, e poi p^{-1}:
Quindi, la permutazione può essere scelta arbitrariamente, e poi è determinata in modo univoco: q[i] = p^{-1}[a^{-1}[i]]. Una permutazione senza punti fissi è detta spostamento (derangement). Il problema si riduce a trovare un derangement tale che la permutazione q = p^{-1} \circ a^{-1} sia anch'essa un derangement.
Scelta casuale. È impossibile esaminare tutte le permutazioni , quindi scegliamo a caso:
generiamo una permutazione casuale; se ha un punto fisso, passiamo al tentativo successivo;
calcoliamo q = p^{-1} \circ a^{-1};
se non ha punti fissi, la coppia è la risposta.
Effettuiamo al massimo tentativi. Se nessuno di essi produce una risposta, stampiamo "Impossible".
Perché funziona. Una risposta non esiste solo in alcuni casi con piccolo:
n = 1 — non esistono affatto permutazioni senza punti fissi;
n = 2 e a = (2, 1) — l'unico derangement p = (2, 1) dà q = (1, 2);
n = 3 e scambia esattamente due elementi. Gli unici derangement di lunghezza sono gli shift ciclici (2, 3, 1) e (3, 1, 2). La composizione di due shift ciclici è di nuovo uno shift ciclico oppure la permutazione identica, quindi non si può ottenere una permutazione che scambia due elementi.
Per n \ge 4, una risposta esiste sempre. Inoltre, come mostra una ricerca esaustiva per piccoli, la frazione di permutazioni adatte tra tutte le permutazioni è almeno (il minimo si raggiunge per n = 4), e al crescere di si avvicina a 1/e^2 \approx 0.135. Pertanto, la probabilità di non trovare una risposta in tentativi non supera (11/12)^{1000} < 10^{-37}, che è praticamente zero. Se non esiste alcuna risposta, nessuno dei tentativi avrà successo, e il programma stamperà correttamente "Impossible".
Esempio
Consideriamo il quarto test: a = (5, 1, 4, 2, 3).
Troviamo la permutazione inversa: se a[i] = x, allora a^{-1}[x] = i. Poiché a[1] = 5, a[2] = 1, a[3] = 4, a[4] = 2, a[5] = 3, otteniamo a^{-1} = (2, 4, 5, 3, 1). La figura mostra la permutazione a sinistra e la sua inversa a destra: tutte le frecce sono invertite.
Generiamo una permutazione casuale senza punti fissi. Sia, per esempio, p = (4, 1, 2, 5, 3). La sua inversa è p^{-1} = (2, 3, 5, 1, 4).
Calcoliamo q[i] = p^{-1}[a^{-1}[i]]:
Otteniamo q = (3, 1, 4, 5, 2). La permutazione non ha punti fissi, quindi la coppia è la risposta. Coincide con la risposta dell'esempio.
Verifichiamo che il trucco riesca, cioè che a[p[q[i]]] = i:
Nel primo test, n = 2 e a = (2, 1), quindi a^{-1} = (2, 1). L'unica permutazione di lunghezza senza punti fissi è p = (2, 1), e p^{-1} = (2, 1). Allora q = p^{-1} \circ a^{-1} = (1, 2) è la permutazione identica, i cui punti sono tutti fissi. La risposta è "Impossible".
Implementazione dell'algoritmo
La funzione print stampa gli elementi dell'array , a partire dal primo.
void print(vector<int>& a)
{
for (int i = 1; i < a.size(); i++)
printf("%d ", a[i]);
printf("\n");
}La funzione is_valid verifica se la permutazione contiene punti fissi.
bool is_valid(vector<int>& v)
{
for (int i = 1; i < v.size(); i++)
if (v[i] == i) return false;
return true;
}Dichiariamo gli array.
vector<int> a, p, p1, q;La parte principale del programma. Leggiamo i dati di input.
scanf("%d", &tests);
while (tests--)
{
scanf("%d", &n);Inizializziamo gli array.
a.resize(n + 1);
p1.resize(n + 1);
p.resize(n + 1);
q.resize(n + 1);Leggiamo la permutazione di input e memorizziamo immediatamente la sua inversa nell'array . Cioè, l'array contiene la permutazione a^{-1}, dove è la permutazione di input.
for (i = 1; i <= n; i++)
{
scanf("%d", &x);
a[x] = i;
}mt19937 è uno dei generatori standard di numeri pseudocasuali in C++ (Mersenne Twister con un periodo di 19937). È molto veloce e ha buone proprietà statistiche, il che lo rende una scelta eccellente per la maggior parte dei compiti che richiedono la generazione di numeri casuali.
mt19937 gen(random_device{}());Nella variabile , segneremo se per il test corrente è stata trovata una soluzione.
found = false;Eseguiamo iterazioni.
for (it = 0; it < 1000; it++)
{Generiamo una permutazione casuale. Per farlo, riempiamo l'array con i numeri 0, 1, 2, ..., e usiamo la funzione shuffle per mescolare gli elementi, a partire dal primo elemento (non nullo). Lavoriamo con permutazioni da a .
iota(p.begin(), p.end(), 0);
shuffle(p.begin() + 1, p.end(), gen);Verifichiamo se la permutazione è valida (cioè, non contiene punti fissi).
if (!is_valid(p)) continue;Calcoliamo la permutazione p_1 = p^{-1}.
for (i = 1; i <= n; i++)
p1[p[i]] = i;Calcoliamo la permutazione q = p_1 ° a^{-1} = p^{-1} ° a^{-1}.
for (i = 1; i <= n; i++)
q[i] = p1[a[i]];Se la permutazione è valida, stampiamo il risultato.
if (is_valid(q))
{
puts("Possible");
print(p);
print(q);
found = true;
break;
}
}Se dopo iterazioni non viene trovata alcuna soluzione, allora non esiste alcuna soluzione.
if (!found) puts("Impossible");
}