题意:小朋友有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\n", dp[cnt[0]][m]/c[n][m]);
}
return 0;
}