实验1动态规划——小明打王者
问题:
小明想要在王者荣耀游戏里晋升一个段位,假设他一共需打了n场比赛,且必须成功赢得至少70%的场次才能成功晋升。假设每场比赛小明获胜的概率分别为p1,p2,…,pn,请帮他算出成功晋级段位的概率是多少?
输入:
参数1:整数num(0<=num<=1000),表示比赛的场数。参数2:整数数组p[num] = {p1,p2,…,pnum},其中pi表示小明有pi%的概率赢得第i场比赛。(0<=pi<=100)
输出:
成功晋级段位的概率,保留小数点后5位,最后结果四舍五入。
分析:
——分析最优子结构:首先确定描述原问题以及其子问题的统一结构。设a[i][j]表示小明已经进行了i场比赛并且赢了j场的概率,那么问题转化为求a[num][0.7*num](其中0.7*num向上取整)一直加到a[num][num]的结果。对于a[i][j]而言,由于其仅有一个精确解而非一个优化问题,因此将其解视为最优解,a[i][j]的解取决于其子结构(子问题)的解:若第i场输了,那么前i场赢j场的概率等于前i-1场赢j场的概率乘以第i场输的概率;若第i场赢了,那么前i场赢j场的概率等于前i-1场赢j-1场的概率乘以第i场赢的概率。可以说,父问题有(最优)解时,子问题也有(最优)解,即具有最优子结构性质。
——确定递推关系:根据最优子结构,可以得出递推公式如下:a[i][j] = a[i-1][0] * (1 - P[i]) , j=0 ; a[i][j] = a[i-1][j] * (1-P[i]) + a[i-1][j-1] * P[i] , j>0;
——计算最优值:初始化最小子问题:a[0][0] = 1,a[k][k] = p1p2…pk;根据递推公式,画出求解矩阵a的顺序:
1 | ||||||
---|---|---|---|---|---|---|
TBD1 | P1 | |||||
TBD2 | TBDnum+1 | … | ||||
TBD3 | … | … | … | |||
TBD4 | … | … | … | … | ||
… | … | … | … | … | … | |
TBDnum | … | … | … | … | … | p1p2…pnum |
首先根据题意,i>=j,则矩阵只需求解对角线及左下半部分。初始化矩阵的对角线。根据递推公式,计算第一列的所有元素需要其上方的元素,计算其它列的元素需要其上方和左上方的元素,因此按照图中的TBD标号顺序逐列求解即可。
——求最优解:由于本题只需给出晋级概率,故不需额外空间来记录问题轨迹并traceback
伪代码:
for i = 1 to num do
a[i][i] = a[i - 1][i - 1] * 0.01d * p[i - 1]
for j = 0 to num - 1 do
for i = j + 1 to num do
if j = 0
a[i][0] = a[i + j - 1][0] * (1 - 0.01d * p[i - 1])
else
a[i][j] = a[i - 1][j] * (1 - 0.01d * p[i - 1]) + a[i - 1][j - 1] * 0.01d * p[i - 1]
for i = ceil(0.7 * num) to num
pass += a[num][i]
代码:
public class GamePassProbability {
// 计算小明晋级成功的可能性
// 输入:int数组p表示每一局的获胜可能性,int数字num表示一共要打多少局
// 输出:小明晋级的可能性
public static double calculatePassProbability(int[] p, int num) {
double pass = 0.0d;// 最终结果
double[][] a = new double[num + 1][num + 1];// 子问题记录矩阵
// 初始化矩阵,对角线赋值
a[0][0] = 1.0d;
for (int i = 1; i <= num; i++) {
a[i][i] = a[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)// 第一列
a[i][0] = a[i + j - 1][0] * (1 - 0.01d * p[i - 1]);
else {// 其他列
a[i][j] = a[i - 1][j] * (1 - 0.01d * p[i - 1]) + a[i - 1][j - 1] * 0.01d * p[i - 1];
}
}
}
a[0][0] = 1.0d;
// 得出结果
int s = (int) Math.ceil(0.7 * num);// 向上取整
for (int i = s; i <= num; i++)
pass += a[num][i];
return pass;
}
public static void main(String[] args) {
int[] p1 = { 50, 50, 50, 50 };
int[] p2 = { 80, 80, 90, 90, 99 };
int[] p3 = {};
double result1 = calculatePassProbability(p1, 4);
double result2 = calculatePassProbability(p2, 5);
double result3 = calculatePassProbability(p3, 0);
System.out.println(result1);
System.out.println(result2);
System.out.println(result3);
}
}
复杂度分析:
——输入规模大小:序列p长度num,此处记为n
——基本操作次数:根据递推式计算a[i][j]
——输入情况:只与问题的规模有关,和问题的输入好坏无关
——时间复杂度:算法是非递归的,直接累加求和就可以得到其时间复杂度为O(n2)
——空间复杂度:额外空间为O(n2)