1. 问题描述
共有n种图案的印章,每种图案的出现概率相同。小A买了m张印章,求小A集齐n种印章的概率。
2. 输入格式
一行两个正整数n和m
3. 输出格式
一个实数P表示答案,保留4位小数。
4. 问题解释
这个问题是一个经典的概率问题,即“收集券问题”,也被称为“餐巾纸问题”。问题的核心在于求解收集到全部不同类型券(在本例中为印章)的概率。这里采用动态规划(Dynamic Programming, DP)解决,因为该问题满足动态规划的两大特性:最优子结构和重叠子问题。
5. 动态规划分析
动态规划表 dp
用于存储中间结果,其中 dp[i][j]
表示购买了 i
张印章时,集齐了 j
种不同印章的概率。
为什么使用动态规划
-
最优子结构:每购买一张新印章时,集齐某种数量印章的概率依赖于之前购买的印章。例如,集齐3种印章的概率可以基于集齐2种或3种印章的概率计算得出。
-
重叠子问题:在计算集齐不同数量印章的概率时,会重复考虑某些情况,如购买了4张印章集齐2种印章的概率会在计算集齐更多种类印章的概率时再次用到。
状态转移方程
- 当
j == 1
时,dp[i][j] = (1/n) ** (i-1)
表示购买i
张印章且全部是同一种的概率。 - 否则,
dp[i][j]
可以从两种情况转移而来:- 继续保持
j
种不同印章:购买的新印章是已有的j
种之一的概率为j/n
,因此概率为dp[i-1][j] * (j/n)
。 - 从
j-1
种变为j
种:购买的新印章是缺失的n-j+1
种之一的概率为(n-j+1)/n
,因此概率为dp[i-1][j-1] * ((n-j+1)/n)
。
- 继续保持
边界条件
dp[i][0] = 0
(不可能购买印章而一种都不集齐)。dp[0][j] = 0
(不购买印章时不可能集齐任何印章)。
6. 实现代码(python)
if __name__ == '__main__':
n, m = map(int, input().split())
dp = [[0] * (n+1) for _ in range(m+1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
if j == 1:
dp[i][j] = (1/n) ** (i-1)
else:
dp[i][j] = dp[i-1][j]*(j/n) + dp[i-1][j-1]*((n-j+1)/n)
print(f"{dp[m][n]:.4f}")