Матч
391, Ключи в коробке (KeysInBoxes)
Дивизион 1,
Уровень 2
Имеется n коробок, пронумерованных с 1 до n, а также n ключей, пронумерованных с 1 до n. i
- ый ключ может открыть только i - ую
коробку. Произвольным образом расположим каждый ключ в отдельную коробку, после
чего закроем все коробки. Имеются m
бомб, каждой из которых можно открыть одну коробку. Открыв при помощи бомбы
коробку и достав из нее ключ, возможно, этим ключом можно открыть еще одну
коробку (если этот ключ не от коробки, в которой он лежал). Таким образом
продолжаем процесс подрыва коробок, доставания ключей и открывание коробок
извлеченными ключами.
В задаче необходимо найти
вероятность того, что можно открыть все коробки. Возвращаемое значение является
строкой “A/B”, содержащую искомую вероятность в виде дроби. Значения A и B
являются натуральными числами, не содержат ведущих нулей и являются взаимно
простыми.
Класс: KeysInBoxes
Метод: string
getAllKeys(int n, int m)
Ограничения: 1 £ n £ 20, 1 £ m £ n.
Вход. Целые числа n и m.
Выход. Строка, содержащая вероятность того, что можно открыть все
коробки.
Пример входа
n |
m |
2 |
1 |
3 |
1 |
4 |
2 |
Пример выхода
“1/2”
“1/3”
“17/24”
РЕШЕНИЕ
математика
Взорвав бомбой коробку, можно
достать ключ из другой коробки. Ключом из другой коробки можно открыть
следующую. И так далее, пока не будет вскрыта коробка, в которой лежит ключ от
первой коробки. Одной бомбой можно открыть такое множество коробок, которые
образуют цикл в перестановке ключей. Количество циклов в перестановке ключей
равно числу бомб, необходимых для открытия всех коробок.
Например, пусть ключи образуют
перестановку (2, 1, 3, 5, 4) = (12)(345). Она состоит из двух циклов, поэтому
для извлечения всех ключей требуется две бомбы.
Количество перестановок из n элементов, имеющих k циклов, равно числу Стирлинга первого
рода s(n, k), которые вычисляются по рекуррентной формуле:
s(n, k) = s(n – 1, k – 1) + (n – 1) * s(n – 1, k)
Искомая вероятность равна количеству
разных перестановок, состоящих из не более чем m циклов, деленная на количество всех перестановок n!.
В массиве s[21][21] вычисляются
числа Стирлинга, причем s[n][k] = s(n, k). В переменной a вычисляется сумма s(n, 1) + s(n, 2) + … + s(n, m), в переменной b – число перестановок n!.
Возвращаем результат в виде строки “a/b”, предварительно сократив числитель и
знаменатель на их наибольший общий делитель.
ПРОГРАММА
#include <cstdio>
#include <string>
#include <memory>
using namespace std;
long long s[21][21];
long long gcd(long long a, long long b)
{
return (!b) ? a : gcd(b,a % b);
}
class KeysInBoxes
{
public:
string getAllKeys(int n, int m)
{
int i, k;
long long
a, b, d;
char str[50];
memset(s,0,sizeof(s));
s[0][0] = 1;
for(i = 1; i <= n; i++)
for(k = 1; k <= n; k++)
s[i][k] = s[i-1][k-1] + (i-1) * s[i-1][k];
// s(n,1) + s(n,2) + ... + s(n,m)
for (a = 0, i = 1; i<= m; i++) a += s[n][i];
for(b = i = 1; i <= n; i++) b *= i; // n!
d = gcd(a,b);
a /= d; b /= d;
sprintf(str,"%lld/%lld",a,b);
return (string)str;
}
};