LINK:开锁魔法II
模拟赛的一道题 没有认真思考比赛就结束了 不过自己赛后的思考也有点漏洞。
首先可以发现图中形成了若干个强联通分量/环 每个环都是独立的 所以只要我们选出K个点分布在这若干个环上就能开锁成功。
求概率 我们可以考虑不计排列数这样我们按顺序统计就可以少乘一个阶乘少除以一个阶乘。
设状态 f[i][j]表示前i个环开了j个锁的概率 转移 f[i][j]=f[i-1][k]C(sz,j-k)C(n-sum,w-j)/C(n-sum+sz,w-k);
可以发现这样做有点复杂 可以设方案数 然后最后除以总方案C(n,w)即可;
那么f[i][j]=f[i-1][k]*C(sz,j-k);
两种方法都需要组合数 但是没模数 据说由于数据的问题直接longdouble存组合数能卡过去。
还有一种方法是 题解给出的在计算概率的时候分配除数 但是我搞不懂为什么那样写 就算了 哎太菜了。
可以发现我们枚举转移综合为O(n)所以总复杂度Tnk.
const int MAXN=310;
int n,fa[MAXN],sz[MAXN],T,k,cnt,q[MAXN];
ldb f[MAXN][MAXN],c[MAXN][MAXN];
inline int getfather(int x){return x==fa[x]?x:fa[x]=getfather(fa[x]);}
int main()
{
freopen("1.in","r",stdin);
c[0][0]=1;
rep(1,300,i)rep(0,i,j)
{
if(!j)c[i][0]=1;
else c[i][j]=c[i-1][j]+c[i-1][j-1];
}
get(T);
while(T--)
{
get(n);get(k);cnt=0;
rep(1,n,i)fa[i]=i,sz[i]=0;
memset(f,0,sizeof(f));
rep(1,n,i)
{
int get(x);
int xx=getfather(x);
int ii=getfather(i);
fa[xx]=ii;
}
rep(1,n,i)
{
int xx=getfather(i);
++sz[xx];
}
rep(1,n,i)if(sz[i])q[++cnt]=sz[i];
f[0][0]=1;
int sum=0;
rep(1,cnt,i)
{
rep(0,sum,j)
rep(1,q[i],l)if(j+l<=k)f[i][j+l]+=f[i-1][j]*c[q[i]][l];
sum+=q[i];sum=min(sum,k);
}
f[cnt][k]=f[cnt][k]/c[n][k];
printf("%.9Lf
",f[cnt][k]);
//cout<<f[cnt][k]<<endl;
}
return 0;
}
值得注意的是 MINGW printf输出不了longdouble cout才行 但是需要背一大堆输出格式。
推荐dev 输出格式为printf("%.xLf"); x表示小数位数。