问题描述:
小明想要在王者荣耀游戏里晋升一个段位,假设他一共需打了n场比赛,且必须成功赢得至少70%的场次才能成功晋升。假设每场比赛小明获胜的概率分别=3为p1,p2,…,pn,请帮他算出成功晋级段位的概率是多少?
输入:
参数1:整数num(0<= num <=1000),表示比赛的场数。
参数2:整数数组p[num] = {p1,p2,…,pnum},其中pi表示小明有pi%的概率赢得第i场比赛。(0 <=pi <= 100)
输出:
成功晋级段位的概率,保留小数点后5位,最后结果四舍五入。
分析
这个问题可以看作:
先求解小明进行i场比赛并且赢了j场的概率
然后再求i=num,j>=num0.7(num0.7向上取整)时的结果
运用我们刚刚学的DP四步骤:
关于DP四步骤可以看动态规划(Dynamic Programming)入门
确定状态
- 最后一步:在进行了i场比赛后赢了j场
- 子问题:当第i场输了的时候,那么在前i-1场比赛,共赢了j场;当第i场赢了的时候,那么在前i-1场比赛,共赢了j-1场.
状态:设f[i][j]表示在进行了i场比赛后赢了j场的概率
转移方程
这里要分两种情况:
当j=0时,f[i][j]=f[i-1][0]*(1-p[i])
当j>0时,f[i][j]=f[i-1][j]*(1-p[i])+f[i-1][j-1]p[i]
初始条件和边界情况
初始条件:
f[0][0]=1、f[k][k]=p1*p2…pk
计算顺序
DP的目的是利用历史记录,不重复计算,所以,我们先枚举j的情况,再枚举i的情况。
代码实现:
package algorithm;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class DPSolution {
public static double probability(int[] p,int num) {
double pass=0.0d;//晋级的概率
double[][] f=new double[num+1][num+1];
//初始条件设定,包括f[0][0]=1,以及f[k][k]=p[1]*p[2]...*p[k]
f[0][0]=1.0d;
for(int i=1;i<=num;++i) {
f[i][i]=f[i-1][i-1]*0.01d*p[i-1];
}
for(int j=0;j<=num-1;++j) {
for(int i=j+1;i<=num;++i) {
if(j==0) {
f[i][0]=f[i+j-1][0]*(1-p[i-1]*0.01d);//j=0时的转移方程
}
else {
f[i][j]=f[i-1][j]*(1-p[i-1]*0.01d)+f[i-1][j-1]*p[i-1]*0.01d;//j>0时的转移方程
}
}
}
int k=(int)Math.ceil(num*0.7);//向上取整
for(int i=k;i<=num;++i) {
pass+=f[num][i];
}
//将返回值pass保留5位小数,同时四舍五入
pass*=100000;
pass+=0.5;
pass=(int)(pass)/100000.0;
return pass;
}
public static void main(String[] args) throws IOException{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int num = 0;
String str = null;
num= Integer.parseInt(br.readLine());
str = br.readLine();
br.close();
int[] p = new int[num];
String[] strArray = str.split(" ");
for (int i = 0; i < num; i++) {
p[i] = Integer.parseInt(strArray[i]);
}
double result=probability(p,num);
System.out.println(result);
}
}
分析
输入规模大小:序列p长度num,此处记为n
基本操作次数:根据递推式计算f[i][j]
输入情况:只与问题的规模有关,和问题的输入好坏无关
时间复杂度:算法是非递归的,直接累加求和就可以得到其时间复杂度为O(n2)
空间复杂度:额外空间为O(n2)(数组大小)