#3124. 开锁(unlock)

题目描述
A君有 $n$ 个盒子,每个盒子被一把锁锁着,每个盒子内都有一把钥匙。对于每个盒子而言有且仅有一把钥匙能打开锁着它的锁,而打开它后便能拿着放置在这个盒子内的钥匙去开启其他盒子。

现在A君打算随机选择 $k$ 个盒子并用魔法将它们打开,并用所得到的钥匙去尝试开启其他所有的盒子 $f$ 开启一个盒子后,新得到的钥匙还能继续尝试使用)。

A君想知道,最终他能打开所有盒子的概率是多少,请你帮助他。

数据范围
$k \le n \le 300,T \le 100$

题解
考虑到只要每个置换圈被取出其中一个,那就可以打开所有盒子

所以设置换数为 $m$,第 $i$ 个置换中的个数为 $s_i$ ,考虑 $dp$ ,设 $f_{i,j}$ 表示前 $i$ 个,取了 $j$ 个的方案数,则$f_{i,j}=\sum_{l=1}^{min(j,s_i)}f_{i-1,j-l} \times C_{s_i}^{l}$

则答案为 $\frac{f_{m,k}}{C_{n}^{k}}$

效率: $O(Tnk)$


代码

#include <bits/stdc++.h>
#define db long double
using namespace std;
db c[305][305],ans;
int T,k,a[305],m,b,n,s[305],f[305];
db g[305][305];
int main(){
    c[0][0]=1;
    for (int i=1;i<=300;i++){
        c[i][0]=1;
        for (int j=1;j<=i;j++)
            c[i][j]=c[i-1][j]+c[i-1][j-1];
    }
    for (scanf("%d",&T);T--;){
        scanf("%d%d",&n,&k);m=0;b=n;
        for (int i=1;i<=n;i++)
            scanf("%d",&a[i]),f[i]=0;
        for (int j,i=1;i<=n;i++)
            if (!f[i]){
                j=a[i];s[f[i]=++m]=1;
                while(!f[j])
                    f[j]=m,j=a[j],s[m]++;
            }
        g[0][0]=1;
        for (int i=1;i<=m;i++)
            for (int j=1;j<=k;j++){
                g[i][j]=0;
                for (int l=1;l<=j && l<=s[i];l++)
                    g[i][j]+=g[i-1][j-l]*c[s[i]][l];
            }
        ans=g[m][k];
        for (int i=1;i<=k;i++) ans/=n,n--,ans*=i;
        printf("%.10LF\n",ans);
    }
    return 0;
}

 

posted @ 2019-08-07 20:15  xjqxjq  阅读(766)  评论(0编辑  收藏  举报