【bzoj5004】開鎖魔法II 組合數學+概率dp

題目描述

有 $n$ 個箱子,每個箱子裏有且僅有一把鑰匙,每個箱子有且僅有一把鑰匙可以將其打開。現在隨機打開 $m$ 個箱子,求能夠將所有箱子打開的概率。


題解

組合數學+概率dp

題目約定了每個點的入度和出度均爲1,因此最終的圖一定是若干個環。每個環都至少選擇一個點即可滿足要求。

預處理出每個環的點數 $c[i]$ 以及其後綴和 $sum[i]$ 。

設 $f[i][j]$ 表示前 $i$ 個環中選出 $j$ 個點,滿足最終條件的概率。初始化 $f[0][0]=1$ 。

枚舉 $i$ 和前 $i-1$ 個環的點數 $j$ 、第 $i$ 個環的點數 $k$ ,那麼:$i\sim n$ 的總方案數爲 $C_{sum[i]}^{m-j}$ ,滿足條件的方案數爲 $c[i]$ 中選出 $k$ 個的方案數乘以剩下部分選出 $m-j-k$ 個的方案數 $C_{c[i]}^k·C_{sum[i]-c[i]}^{m-j-k}$ 。

整理一下即可得到dp方程 $f[i][j+k]\leftarrow f[i-1][j]·\frac{C_{c[i]}^k·C_{sum[i]-c[i]}^{m-j-k}}{C_{sum[i]}^{m-j}}$ 。

最後的答案就是 $f[n][m]$ 。

其中組合數直接使用double存據說能過,然而我比較慫,因此存的是階乘的 $\ln$ ,求的時候再 $\text{exp}$ 回去。

時間複雜度 $O(n^3)$ 。

#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 310
using namespace std;
int a[N] , c[N] , vis[N] , sum[N];
double fac[N] , f[N][N];
int main()
{
    int T;
    scanf("%d" , &T);
    while(T -- )
    {
        memset(vis , 0 , sizeof(vis));
        memset(f , 0 , sizeof(f));
        f[0][0] = 1;
        int n , m = 0 , p , i , j , k;
        scanf("%d%d" , &n , &p);
        for(i = 1 ; i <= n ; i ++ ) scanf("%d" , &a[i]) , fac[i] = fac[i - 1] + log(i);
        for(i = 1 ; i <= n ; i ++ )
        {
            if(!vis[i])
            {
                c[++m] = 0;
                for(j = i ; !vis[j] ; j = a[j])
                    vis[j] = 1 , c[m] ++ ;
            }
        }
        sum[m + 1] = 0;
        for(i = m ; i ; i -- ) sum[i] = sum[i + 1] + c[i];
        for(i = 1 ; i <= m ; i ++ )
            for(j = max(i - 1 , p - sum[i]) ; j < p && j <= n - sum[i] ; j ++ )
                for(k = 1 ; k <= c[i] && j + k <= p ; k ++ )
                    f[i][j + k] += f[i - 1][j] * exp(fac[c[i]] + fac[sum[i] - c[i]] + fac[p - j] + fac[sum[i] - p + j] - fac[k] - fac[c[i] - k] - fac[p - j - k] - fac[sum[i] - c[i] - p + j + k] - fac[sum[i]]);
        printf("%.9lf\n" , f[m][p]);
    }
    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章