题目链接:HDU-3625 Examining the Rooms
题意
有$n$把对应开$n$个房间的钥匙,每把钥匙随机出现在一个房间里,一个房间里有且仅有一把钥匙。我们现在手上没有钥匙,但有$k$次机会可以炸开一个房间,且不能炸1号房间,每次炸开一个房间后,拿到里面的钥匙去开另一个房间,直到不能打开新的房间,再进行炸开房间的操作......给出$n$和$k$,问我们能够打开所有房间的概率。
思路
钥匙放在哪个房间是个排列,比如我们用2,3,1,4表示1号房间里放着2号房间的钥匙,2号房间里放着3号房间的钥匙......可以发现这个排列里有两个循环节${2,3,1}$和${4}$,或者称之为两个环,对于每个环我们炸一次就可以打开环上所有的房间,所以成功的情况就是环的个数小于等于$k$并且1号钥匙不在1号房间里。
把$n$把不同的钥匙排成$i$个循环节的排列的方法数就是第一类斯特林数的内容,我们用$s(n,i)$表示。
不能炸1号房间也就是排列的1号点不能单独成环。当排列有$i$个环,1号点单独成环的情况就是其余$n-1$个点成$i-1$个环,即$s(n-1,i-1)$。
所以答案为:
$$
frac{sum_{i=1}^k{(s(n,i)-s(n-1,i-1))}}{n!}
$$
第一类斯特林数递推式:$s(n,i)=(n-1)s(n-1,i)+s(n-1,i-1)$
代码实现
#include <cstdio> typedef long long LL; const int N = 25; LL s[N][N], f[N]; int main() { s[0][0] = f[0] = 1; for (int i = 1; i < N; i++) { f[i] = f[i-1] * i; for (int j = 1; j <= i; j++) { s[i][j] = s[i-1][j-1] + s[i-1][j] * (i - 1); } } int t; scanf("%d", &t); while (t--) { int n, k; LL sum = 0; scanf("%d %d", &n, &k); for (int i = 1; i <= k; i++) sum += s[n][i] - s[n-1][i-1]; printf("%.4f ", double(sum) / f[n]); } return 0; }