动态规划:
资源限制
时间限制:1.0s 内存限制:256.0MB
问题描述
共有n种图案的印章,每种图案的出现概率相同。小A买了m张印章,求小A集齐n种印章的概率。
输入格式
一行两个正整数n和m
输出格式
一个实数P表示答案,保留4位小数。
样例输入
2 3
样例输出
0.7500
数据规模和约定
1≤n,m≤20
题目意思简洁明了。。。。
动态规划:
1.设置状态(看是一维数组还是二维数组,一般的题目都是二维数组,经验之谈)
2.找状态之间的关系,
3.写代码
这其中1,2是最难的,首先把状态想出来这一步就可以劝退很多人了,怎么想状态呢,把我平时做dp的思路跟你们说一下:
首先看题目,有买的印章数和印章种数两个变量,就自然而然地想成二维数组dp[i] [j],数组有了。状态呢?题目要求的是概率嘛,那我们dp数组的值就是概率。那么,i,j怎么说呢?题目说了小A买了m张,要凑齐n种,那么我们就设:dp[i] [j]表示买 i 张凑齐 j 种印章的概率。
这样,状态就大致想出来了!(为什么是大致?因为不确定我们的思路是不是对的,状态是根据题意、经验和感觉设想出来的),可能就是要多做吧。
状态设想出来,二维数组的表格先画出来(如上图)。然后寻找初始状态(一般都是i == 1,2,3或者j == 1,2,3这种i,j的值很小的)。
题目中有 n 种印章, 每种概率是 1/n;
先来看i
再来看j=1的时候:
i=1:dp[i] [1]表示的意思是买 i 张凑齐 1 种的概率,很明显 i == 1 && j == 1 的时候dp[1] [1] = 1,因为你买一张是无论如何都可以凑齐一种的,是吧。
i > 1:i > 1 && j == 1. 意思就是买 i 张凑齐 1 种印章的概率,意味着买的这 i 张印章是同一种,概率就是 (1/n)^i,但是,我们这 i 张要么是第一种,要么是第二种…( 要么是第k种 ,1 <= k <= n )。最后还要乘上n,所以这个情况 dp[i] [j] = (1/n)^(i-1)
初始状态大致就上面这些了:↑
最后来看中间的状态 dp[i] [j]:
我们买的第 i 张,有两种状态,一:跟前面买的 i-1 张中有重复的 二:跟前面买的 i-1 张中没有重复的
什么意思呢? 比如:(总共有5种印章,标号1,2,3,4,5) 我正在买第5张,前面四张凑齐了1,2,3,4号,那么第5张如果也是1,2,3,4号中的一个就表示重复的;如果第5张是5号,就表示没有重复的。
一:有重复的:就表明前面买的 i-1 张,已经凑齐了 j 种。dp[i] [j] = dp[i-1] [j] * ( j / n ); j/n是什么意思?
二:无重复的
前面买的 i-1 张 凑齐了 j-1 种, 我们买的第 i 张与前面的不重复,就又凑齐了一种:
dp[i] [j] = dp[i-1] [j-1] * (n-j+1) / n;
然后把两种情况加起来就是dp[i] [j] 的概率了!!
#include <iostream>
#include <cmath>
using namespace std;
double dp[25][25], p;
int main()
{
//记住是小数啊,要*1.0进行类型转换的
int n, m;
cin >> n >> m;
p = 1.0 / n; //每种出现的概率
for ( int i = 1; i <= m; ++i ) {
for ( int j = 1; j <= n; ++j ) {
if ( i < j ) dp[i][j] = 0;
if ( j == 1 ) {
dp[i][j] = pow (p, i-1); //p^(i-1)
}
else {
dp[i][j] = dp[i-1][j] * (j*1.0/n) + dp[i-1][j-1] * ((n-j+1)*1.0/n);
}
}
}
// cout << "dp\n";
// for ( int i = 1; i <= m; ++i ) {
// for ( int j = 1; j <= n; ++j ) {
// printf("%.2lf ",dp[i][j]);
// }
// cout << endl;
// }
printf("%.4lf ",dp[m][n]);
return 0;
}
总结:
1.数组确定(一维,还是二维,三维,n维也有…一般没有)
2.数组值(题目求什么,我们数组表示的值一般就是什么)
3.状态设想 (dp[i] [j]表示 什么什么 i 什么什么 的时候, 什么什么 j 什么什么的时候 的 值)
4.初始状态(就是找特殊值,i=1,2,3 j=1,2,3)
5.中间状态 ( i, j 与 i-1, i-2, j-1, j-2…)
6.写代码