[hihocoder 1075] 开锁魔法III

 

题意:小朋友有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;
}

 

posted @ 2017-09-15 20:19  HLX_Y  阅读(164)  评论(0编辑  收藏  举报