试题 算法训练 印章
资源限制
时间限制:1.0s 内存限制:256.0MB
问题描述
共有n种图案的印章,每种图案的出现概率相同。小A买了m张印章,求小A集齐n种印章的概率。
输入格式
一行两个正整数n和m
输出格式
一个实数P表示答案,保留4位小数。
样例输入
2 3
样例输出
0.7500
数据规模和约定
1≤n,m≤20
题解
思路
刚刚看到题目的时候,一开始以为是用高中学的概率论,组合数来做,但是后来发现自己实在是想不到,太复杂了,而且概率论也很久没学了,那么没办法了,只能是用动态规划来解决了
动态规划主要分为两步
第一步:确定初始状态
首先,我们知道,如果挑出一张牌,想要只抽中一种牌的概率肯定为1,接着,我们往下继续分析
我们现在思考一下,如果挑出i张牌,最后只抽中1种牌的概率是多少?
第一次肯定是1,第二次还是得抽中这张牌,那么概率就是 1 n \frac{1}{n} n1,第三次还是这张牌,概率为 ( 1 n ) 2 (\frac{1}{n})^2 (n1)2,推算下来,我们想要i张牌全部抽中1种牌的概率就是 ( 1 n ) i − 1 (\frac{1}{n})^{i-1} (n1)i−1
至此,初始状态已经确定了
特殊的,当 i < j i<j i<j时,想要从使 i i i张牌中有 j j j种牌,一定是不存在的,则概率为0
第二步:推算动态转移方程
这题动态转移方程有点难推理,但是也不是不能推理
当前我们要抽i张牌,并且要保证这i张牌中正好有j种牌,现在我们要对抽 i − 1 i-1 i−1张牌的状态进行分析
- 假设我们在抽第 i − 1 i-1 i−1张牌的时候,已经有j种牌了,那么此时,我们在抽第 i i i张牌时还要保证这i张牌正好有j种牌,那么我们这时候不得不抽中这j种牌中任何一张,所以概率是 d p [ i − 1 ] [ j ] ∗ j n dp[i-1][j]*\frac{j}{n} dp[i−1][j]∗nj
- 假设我们在抽第 i − 1 i-1 i−1张牌是,手上只有 j − 1 j-1 j−1种牌,那么此时,我们要保证这 i i i张牌中正好有j种牌,我们只能从这 j − 1 j-1 j−1种牌外再抽出一张,概率为 d p [ i − 1 ] [ j − 1 ] ∗ n − j + 1 n dp[i-1][j-1] * \frac{n-j+1}{n} dp[i−1][j−1]∗nn−j+1
最后我们可以得出动态转移方程:
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
]
∗
j
n
+
d
p
[
i
−
1
]
[
j
−
1
]
∗
n
−
j
+
1
n
dp[i][j] = dp[i-1][j]*\frac{j}{n}+dp[i-1][j-1]*\frac{n-j+1}{n}
dp[i][j]=dp[i−1][j]∗nj+dp[i−1][j−1]∗nn−j+1
至此,此题已经分析完毕,剩下按照基本方法推就行了
java代码
import java.util.Scanner;
/**
* @author 王宇哲
*/
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int m = scanner.nextInt();
//第一维界表示的是买彩票的张数,第二维界表示要凑齐的张数
double[][] dp = new double[m+1][n+1];
//买一张只凑齐1种的概率一定为1
dp[1][1] = 1;
//初始化
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(i<j){
dp[i][j] = 0;
}
//买i张(只)凑齐1张的概率
if(j == 1 && i != 1){
dp[i][j] = Math.pow(1.0/n,i-1);
}
}
}
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
//防止重复赋值
if(!(j == 1 && i != 1) && !(i==1 && j==1)){
//前面j种已经凑齐了,又抽到了前面j中
dp[i][j] += dp[i-1][j]*((double) j/n);
//前面i张已经凑齐了j-1中,这次抽到其他的牌的概率
dp[i][j] += dp[i-1][j-1] * ((n-j+1.0)/n);
}
}
}
System.out.printf("%.4f",dp[m][n]);
}
}