题目描述
A君有 n n n 个盒子,每个盒子被一把锁锁着,每个盒子内都有一把钥匙。对于每个盒子而言有且仅有一把钥匙能打开锁着它的锁,而打开它后便能拿着放置在这个盒子内的钥匙去开启其他盒子。
现在A君打算随机选择 k k k 个盒子并用魔法将它们打开,并用所得到的钥匙去尝试开启其他所有的盒子 f f f 开启一个盒子后,新得到的钥匙还能继续尝试使用)。
A君想知道,最终他能打开所有盒子的概率是多少,请你帮助他。
数据范围
k ≤ n ≤ 300 , T ≤ 100 k \le n \le 300,T \le 100 k≤n≤300,T≤100
题解
考虑到只要每个置换圈被取出其中一个,那就可以打开所有盒子
所以设置换数为 m m m,第 i i i 个置换中的个数为 s i s_i si ,考虑 d p dp dp ,设 f i , j f_{i,j} fi,j 表示前 i i i 个,取了 j j j 个的方案数,则 f i , j = ∑ l = 1 m i n ( j , s i ) f i − 1 , j − l × C s i l f_{i,j}=\sum_{l=1}^{min(j,s_i)}f_{i-1,j-l} \times C_{s_i}^{l} fi,j=∑l=1min(j,si)fi−1,j−l×Csil
则答案为 f m , k C n k \frac{f_{m,k}}{C_{n}^{k}} Cnkfm,k
效率: O ( T n k ) O(Tnk) 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;
}