題意
n種bug,s個子程序。每天隨機找一個bug,允許重複。求每種bug都找到,每個程序都找到bug的期望天數
分析
這是一個期望DP,對於期望DP通常是逆向推。即用dp值表示從當前狀態到達終止狀態的期望值,然後由終止狀態推導至初始狀態。
這裏我們可以以已找到bug種類數量和已完成子程序數量作爲狀態,dp值表示從當前狀態到達終止狀態的期望天數。
對於一個狀態(i,j),i表示已找到的bug種類數量,j表示已完成子程序數量。它可以轉移到4個狀態
- (i,j)。pa = i/n * j/s
- (i+1,j)。pb = (n-i)/n * j/s
- (i,j+1)。pc = i/n * (s-j)/s
- (i+1,j+1)。pd = (n-i)/n * (s-j)/s
從當前狀態到終止狀態的期望值可以分爲兩個部分。
1. 從當前狀態到達下一狀態集的期望值
令p1 = pa,爲當前狀態轉移至本狀態的概率。而p2 = pb + pc + pd,爲當前狀態轉移到下一狀態集的概率(p1 + p2 = 1)。所以從當前狀態到達下一狀態的天數爲n的概率爲 p = p2 * p1n-1 。所以期望值爲:
2. 由下一狀態集到達終止狀態的期望值
使用逆推,我們在當前狀態可以知道下一狀態集中每個狀態到達終止狀態的期望值。因此只需計算到達的下一狀態的概率,加權求和即可。對pb,pc,pd進行歸一化,可知下一狀態的概率分別是,,。
如此,dp[i][j] = exp1 + exp2。
代碼
// 期望DP, dp值表示從當前狀態到達終止狀態的期望值
// 逆向求期望, 從終態推導至初態
#include <iostream>
using namespace std;
const int MAX_N = 1e3;
const int MAX_S = 1e3;
// 狀態: 表示已找到bug種類數量, 已完成子程序數量
// dp: 表示從當前狀態到目標狀態的期望時間
double dp[MAX_N+10][MAX_S+10];
int main()
{
int n, s;
scanf("%d%d", &n, &s);
double nn = n, ss = s;
dp[n][s] = 0;
for (int i = n; i >= 0; i--)
for (int j = s; j >= 0; j--)
{
if (i == n && j == s) continue;
double ii = i, jj = j;
// p1表示留在當前狀態的概率
// p2表示離開當前狀態的概率(到達下個狀態)
// 所以離開當前狀態的天數爲n的概率p(n) = p2 * p1^(n-1)
// 所以離開當前狀態的期望是 sum(i * p2 * p1^(i-1))
// 通過差比數列求和公式計算出離開當前狀態的期望天數是 1+p1/p2
double p1 = (ii/nn) * (jj/ss);
double p2 = 1 - p1;
dp[i][j] = 1 + p1/p2;
// 歸一化達到的下個狀態的概率, 計算從下個狀態到達終態的期望天數
dp[i][j] += (nn-ii)/(nn) * (jj/ss) / p2 * dp[i+1][j];
dp[i][j] += (ii/nn) * ((ss-jj)/ss) / p2 * dp[i][j+1];
dp[i][j] += ((nn-ii)/(nn)) * ((ss-jj)/ss) / p2 * dp[i+1][j+1];
}
printf("%.4f\n", dp[0][0]);
}