题意
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]);
}