HDU-3625 Examining the Rooms (第一类斯特林数)
题目链接: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\n", double(sum) / f[n]); } return 0; }
作者:_kangkang
本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。