题意:小朋友有N个盒子,每个盒子装着打开第ai个盒子的钥匙,小朋友一开始可以使用洪荒之力打开k个盒子,求他能开完所有盒子的概率
题解:
DP+组合数
首先几个盒子通过多次打开一定会形成一个环,设每个环的盒子数为cnt[i]
状态:dp[i][j]表示前i个盒子用j个钥匙打开的方案数
转移:dp[i+1][j+k]=∑dp[i][j]*C[cnt[i]][k]
ps:dp数组和C数组要开double,double的上界大约可以到1*10^308(汗)
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<algorithm> #include<cmath> #define ll long long using namespace std; const int N = 310; int T,n,m,cnt[N],a[N]; double dp[N][N],c[N][N]; bool vis[N]; int gi() { int x=0,o=1; char ch=getchar(); while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar(); if(ch=='-') o=-1,ch=getchar(); while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar(); return o*x; } void pre() { c[0][0]=1.0; for(int i=1; i<=300; i++) { c[i][0]=c[i][i]=1; for(int j=1; j<i; j++) c[i][j]=c[i-1][j]+c[i-1][j-1]; } } int main() { pre(); int T=gi(); while(T--) { memset(cnt,0,sizeof(cnt)); memset(dp,0,sizeof(dp)); memset(vis,0,sizeof(vis)); int n=gi(),m=gi(),tmp; for(int i=1; i<=n; i++) a[i]=gi(); for(int i=1; i<=n; i++) { if(vis[i]) continue; vis[i]=1,tmp=a[i],cnt[++cnt[0]]=1; while(!vis[tmp]) cnt[cnt[0]]++,vis[tmp]=1,tmp=a[tmp]; } dp[0][0]=1.0; for(int i=1; i<=cnt[0]; i++) for(int j=0; j<=m; j++) for(int k=1; j+k<=m && k<=cnt[i]; k++) dp[i][j+k]+=dp[i-1][j]*c[cnt[i]][k]; printf("%.4f ", dp[cnt[0]][m]/c[n][m]); } return 0; }