吐槽
刷这些题让感觉我回到了高中时代,真的处处是公式!现在大三了,数学公式真的忘记七七八八了,忒难受的。平时在力扣刷题,力扣题目风格跟这些竞赛题目有很大的差别,多少有点接受不了,说句实在话,这些题跟我在力扣刷的那些中等题还要难些..但是没办法呀,谁叫我想去北京呢?只能一步一脚印了!推荐大家一个刷题网站AcWing,里面的东西,真的good!
题目
资源限制
时间限制:1.0s 内存限制:256.0MB
问题描述
共有n种图案的印章,每种图案的出现概率相同。小A买了m张印章,求小A集齐n种印章的概率。
输入格式
一行两个正整数n和m
输出格式
一个实数P表示答案,保留4位小数。
样例输入
2 3
样例输出
0.7500
数据规模和约定
1≤n,m≤20
分析
这是一道动态规划的问题。根据题意“小A买了m张印章,求小A集齐n种印章的概率。”我们就要想到使用二维数组。
二维数组功能多多,能够记录买了多少张印章,集齐了多少种印章,以及出现这种情况的概率。比如小A买了3张印章,集齐2种印章的概率为0.75,用个二维数组(dp)就可以这么表示:dp[3][2] = 0.75。求买了m张印章,集齐n种印章的概率为dp[m][n],我们只要求出dp[m][n]就能得出正确答案。
dp[m][n] 表示买了m张印章,集齐n种印章的概率。而选取每个印章的概率P就是1/n。
分类讨论
很显然,我们不能马上得出结果,必须要先进行分类讨论。
1. 集齐数大于购买数
要集齐的种类还大于购买的数量那样是不可能集齐的。转化成计算机语言就是
if j > i:
dp[i][j] = 0
2.集齐印章种数为1
中华文化博大精深
其实,集齐种数为1是有歧义的。
第一种
我们先来看一下题目,“共有n种图案的印章,每种图案的出现概率相同。小A买了m张印章,求小A集齐n种印章的概率。”第一种集齐印章种数为1的情况是指,输入的n为1,这样的话就是只有1种图案印章,小A无论买多少张印章,只要买了,必定能集齐印章。 转换成计算机语言就是
if n == 1:
dp[X][n] = 1 # X表示任意大于0的整数
我想一开始读者就以为我说的是这种情况,但不是的,这种属于特殊情况,我说的集齐印章种数为1是另外一种。
第二种
因为我们不确定n的值,这里说的集齐印章种数为1指的是,在不确定图案种类数(n)的情况下集齐的印章种数为1。比如说有三种图案云,鸟,花,你买了四张印章,只集齐一种印章的概率就与第一种情况的概率不同,不等于1。你只能四张都是买云或花或鸟。
也许这里就有同学不理解我为什么要引入“集齐印章种数为1”这个概念了。因为我们求dp[m][n]就必须从dp[1][1]..dp[2][1]开始。试想一下,你要求出抛三次硬币都是正面的概率,是不是要通过抛两次硬币都是正面的概率 乘上 第三次抛硬币是正面的概率 得出结果?而两次都是正面又要通过一次是正面的概率乘上第二次是正面的概率..而且“集齐印章种数为1”在整个大类的分类讨论中还是属于特殊情况,我们必须要考虑。
捋清楚什么是特殊情况,什么是特殊情况里面的特殊情况就能接着往下看了。
买的印章数
其实买的印章数也要分情况
- 当买的印章数是1,集齐印章种数也是1。那么买齐的概率一定是1.
- 当买的印章数大于1,集齐的印章种数是1,那样就必须买的印章数都相同,集齐的种数才能是1。
我们以 dp[i][1] (表示买i张印章,集齐一种印章的概率)为例分析第二种情况的概率:每种印章出现的概率是 ,买了 i 张只买一种图案(比如 云)的概率为
,买了i张且买的是任意种图案的概率为
* n。化简得
。转换成计算机语言为
if j == 1:
dp[i][j] = (1/n) ** (i-1)
3.正常情况
去掉上面的特殊情况,剩下的就是正常情况(中间状态)。而正常情况下面又有两种情况,没了灭了,真的是最后这两种情况了。
以dp[i][j](买i张印章,集齐j种印章的概率)为例,在第i次买印章的过程中,能买到重复的印章,也能买到没有重复的印章,两者的概率不同,他两就包含了正常情况下所有可能出现的情况。dp[i][j]的值就是二者概率值相加。
买到重复印章
如果买到重复印章,那么在上一次购买中(第i - 1次)已经凑齐了 j 种印章,因此P(重) = dp[i-1][j] * (j / n)
j / n表示第i次购买中买到重复印章的概率
可能有同学不理解为什么是 dp[i-1][j],因为第i次已经凑齐了j个印章(dp[i][j]),而第i次又是重复选取了印章,那么第i-1次必然就是凑齐了j个印章。
买到新印章
如果买到新印章,那么在上次购买中(第 i-1 次)凑齐了 j-1 种印章(注意,我是以dp[i][j]为例,第i次购买凑齐了j种印章,而这一次又是买到新印章,那么第 i-1 次就是凑齐了j-1种印章)因此,P(新) = dp[i-1][j-1] * (n-(j-1)) / n
(n-(j-1)) / n 表示第i次购买中买到新印章的概率。(即除重复印章外的印章)
结合两种情况,dp[i][j] = dp[i-1][j] * (j / n) + dp[i-1][j-1] * (n-(j-1)) / n。
正常情况就是除特殊情况外的情况,那么这里转化为计算机语言就是
else:
dp[i][j] = dp[i-1][j] * (j / n) + dp[i-1][j-1] * (n - (j-1)) / n
考虑完以上情况我们就能得出正确答案了,现在来试试吧!
代码实现
如果你使用的不是python语言,还需要额外考虑数字类型之间的转换,避免因为精度不同影响结果。
n, m = map(int, input().split())
# 注意在range里面都要+1,因为数组是从0开始的,否则考虑不到。另外 n, m 两个位置不能变
dp = [[0 for i in range(n + 1)] for j in range(m + 1)]
# 两个for 之间 m, n 位置也不能变
for i in range(1, m + 1):
for j in range(1, n + 1):
if j > i:
# 因为我们提前定义好了,默认都是0。所以如果集齐数大于购买数可以直接跳过。
continue
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("{:.4f}".format(dp[m][n]))
总结
这笔记我也花了不少时间才整理好的,都是文字性的东西,比较难理解。如果有不懂的地方可以手写模拟一下计算过程。