蓝桥杯 算法训练 印章
问题描述
共有n种图案的印章,每种图案的出现概率相同。小A买了m张印章,求小A集齐n种印章的概率。
输入格式
一行两个正整数n和m
输出格式
一个实数P表示答案,保留4位小数。
样例输入
2 3
样例输出
0.7500
数据规模和约定
1≤n,m≤20
本题使用动态规划算法,动态规划无非是以下三步,
- 确认状态,即dp数组中的值的含义
- 设计状态转移方程(递推公式)。
- 设置dp数组的初值
其中前2步尤为关键。除此之外,对于同一个问题使用用动态规划算法,可以设计出不同的状态和对应的状态转移方程(即dp数组所代表的含义和递推公式不唯一)
解题思路
首先,题目说买m个印章,凑齐n种不同的印章。有2个变量,自然的想到使用二维数组(一般的题目都是用二维数组),而题目是求买m个印章,集齐n种印章的概率。所以可以用dp[i][j]代表买i个印章,集齐j种印章的概率。
于是就有了以下表格
dp(i,j):
(1,1) | (1,2) | (1,3) | (1,4) |
---|---|---|---|
(2,1) | (2,2) | (2,3) | (2,4) |
(3,1) | (3,2) | (3,3) | (3,4) |
(4,1) | (4,2) | (1,3) | (4,4) |
分析
- 红色圈内,当j=1时,总共只有1种印章,无论买多少个印章都必定可以凑齐,所以概率p=1
- 橙色圈内,当i<j时,购买买的印章数量少于印章的种类数,不可能凑齐,概率p=0
所以,只剩下绿色圈内的值未知,需要使用状态转移方程求出。
在求dp[i][j]时,需要思考它与dp数组中其他值的关系。由概率论相关知识可知,当求买m个印章,凑不齐(注意是凑不齐)n种印章的
概率时,买到的印章共有i^m 种结果,即分母为 i^m。分子则为
∑
k
=
1
i
−
1
(
C
i
k
k
m
d
p
[
i
]
[
k
]
)
\sum_{k=1}^{i-1}(C_i^kk^mdp[i][k])
k=1∑i−1(Cikkmdp[i][k])
而凑齐的概率则为(1-凑不齐的概率)
由此可以看出分子只与dp数组的某一行有关,所以其实只需要一维数组就可以了,一维数组的值代表凑齐n种印章的概率。(购买的印章数量固定不变)
以买4个印章凑齐n种为例:
所以购买m个印章,凑齐i种印章的概率dp[i] 为
d
p
[
i
]
=
∑
k
=
1
i
−
1
(
C
i
k
k
m
d
p
[
k
]
)
i
m
dp[i]=\frac{\sum_{k=1}^{i-1}(C_i^kk^mdp[k])}{i^m}
dp[i]=im∑k=1i−1(Cikkmdp[k])
具体代码如下:
#include<stdio.h>
#include<math.h>
double dp[22];//一维数组,dp[i]:买i张印章,凑齐n种印章的概率
/*
为什么用一维数组?
因为在此解法所用的递推公式中,
买m张印章,凑齐n种印章的概率只与买m张印章,凑齐n-1种,n-2种...1种 的概率有关 ,
与买x(x!=m)张印章凑齐y(y>=1)种印章的概率无关
*/
long long c_fun(int n,int m)//求组合数C(n,m);
{
long i;
long long ans_n=1,ans_m=1,ans_nm=1;
for(i=2;i<=n;i++)
ans_n=ans_n*i;
for(i=2;i<=m;i++)
ans_m=ans_m*i;
for(i=2;i<=n-m;i++)
ans_nm=ans_nm*i;
return ans_n/(ans_m*ans_nm);
}
double fun(int n,int m)//求解印章问题
{
int i,j;
double t=0;
dp[1]=1.0;//只需凑齐1种,必定可以凑齐
for(i=2;i<=n;i++)//循环,计算需凑齐2,3,4...n种的情况
{
t=0;
for(j=1;j<i;j++)//
{
t=t+c_fun(i,j)*dp[j]*pow(j,m);//递推公式
}
dp[i]=1-t/pow(i,m);
//要用1去减,因为前面递推公式计算出来的是凑不齐的概率,dp数组存的是凑齐的概率
}
return dp[n];//返回结果
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);//共n种印章,买m张
fun(n,m);//求解
printf("%.4lf\n",dp[n]);//dp[n]即为结果
return 0;
}
另一种解法:使用二维数组,dp[i] [j]表示买 i 张凑齐 j 种印章的概率,具体请看另一篇博客蓝桥杯 算法训练 印章
总结:
对于动态规划最重要的就是画图填表,求递推方程。除此之外,通往罗马的路不止一条,对于一个问题,看问题的角度不同就会有不同的解法。