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;
}
View Code

 

posted @ 2019-08-08 01:33  _kangkang  阅读(157)  评论(0编辑  收藏  举报