题目描述
给定3个参数,N,M,K 怪兽有N滴血,等着英雄来砍自己
英雄每一次打击,都会让怪兽流失[0-M]的血量
到底流失多少?每一次在[0~M]上等概率的获得一个值
求K次打击之后,英雄把怪兽砍死的概率
题目解析
思路:死亡的次数 / 总的次数
暴力递归
总次数:
- 每砍一刀,都有0~M种可能(每一刀会生成一颗十叉树)
- 一共K刀,也就是一共有K次M+1的分支,也就是总的可能次数是 ( M + 1 ) K (M + 1)^K (M+1)K
死亡的次数:
- 什么时候死亡呢?当没血了(rest)怪兽就会死亡
- 问题是:rest可以为负数吗?如果没有砍足K刀,怪兽就死掉了,应该怎么处理呢?
回答:
- 因为总次数中包含了rest < 0时的次数,所以rest可以为负数
- 而且总次数包含了一定要砍足K刀,所以理论上我们应该继续处理
class Solution {
int monster_die(int time, int hp, int M){
if(time == 0){
return hp <= 0 ? 1 : 0;
}
int ways = 0;
for (int i = 0; i <= M; ++i) {
ways += monster_die(time - 1, hp - i, M);
}
return ways;
}
public:
double KillMonster(int N, int M, int K){
int die = monster_die(K, N, M);
int all = std::pow(M + 1, K);
return (double )die/(double )all;
}
};
当然,我们可以进行优化:
- 如果还有time刀没有砍但是怪兽已经死亡,那么剩下的分支总是可以提前算出来,为 ( M + 1 ) t i m e (M + 1)^{time} (M+1)time
class Solution {
int monster_die(int time, int hp, int M){
if(time == 0){
return hp <= 0 ? 1 : 0;
}
if(hp <= 0){
return std::pow(M + 1, time);
}
int ways = 0;
for (int i = 0; i <= M; ++i) {
ways += monster_die(time - 1, hp - i, M);
}
return ways;
}
public:
double KillMonster(int N, int M, int K){
int die = monster_die(K, N, M);
int all = std::pow(M + 1, K);
return (double )die/(double )all;
}
};
暴力递归改动态规划
(1)准备表。分析递归函数的可变参数
int monster_die(int time, int hp, int M)
- time:0~K
- hp:0~N
所以要准备一个二维数组:
std::vector<std::vector<int>> dp(K + 1, std::vector<int>(N + 1, 0));
(2)返回值:
- dp[K][N]/ all
(3)填表
(3.1)先初始化表:dp[0][0] = 1,dp[0][1…] = 0
if(time == 0){
return hp <= 0 ? 1 : 0;
}
(3.2)填表
- dp[time][dp] = dp[time - 1][hp - (0…M)]
- 所以,应该从上到下,从左往右填写
if(hp <= 0){
return std::pow(M + 1, time);
}
int ways = 0;
for (int i = 0; i <= M; ++i) {
ways += monster_die(time - 1, hp - i, M);
}
- 又因为第0行已经初始化了,所以我们从第一行开始填写
class Solution {
public:
double KillMonster(int N, int M, int K){
std::vector<std::vector<int>> dp(K + 1, std::vector<int>(N + 1, 0));
dp[0][0] = 1; // [0 time][1....hp] = 0
for (int time = 1; time <= K; ++time) {
dp[time][0] = (long) std::pow(M + 1, time); // [1...K][0 hp] =
for (int hp = 1; hp <= N; ++hp) {
long ways = 0;
for (int i = 0; i <= M; ++i) {
if(hp - i <= 0){
ways += std::pow(M + 1, time - 1);
}else{
ways += dp[time - 1][hp - i];
}
}
dp[time][hp] = ways;
}
}
int kill = dp[K][N];
int all = std::pow(M + 1, K);
return (double )kill/(double )all;
}
};
优化枚举
- d p [ 3 ] [ 0 ] = p o w ( M + 1 , 3 ) dp[3][0] = pow(M + 1, 3) dp[3][0]=pow(M+1,3)
- d p [ 3 ] [ 1 ] = d p [ 2 ] [ 1 ] + d p [ 2 ] [ 0 , − 1 , − 2...... ( 1 − M ) ] dp[3][1] = dp[2][1] + dp[2][0,-1, -2......(1 - M)] dp[3][1]=dp[2][1]+dp[2][0,−1,−2......(1−M)]
- d p [ 3 ] [ 1 ] = d p [ 2 ] [ 1 ] + d p [ 2 ] [ 0 , − 1 , − 2...... ( 2 − M ) ( 1 − M ) ] dp[3][1] = dp[2][1] + dp[2][0,-1, -2......(2 - M)(1 - M)] dp[3][1]=dp[2][1]+dp[2][0,−1,−2......(2−M)(1−M)]
- d p [ 3 ] [ 1 ] = d p [ 2 ] [ 1 ] + d p [ 2 ] [ 0 , − 1 , − 2...... ( 2 − M ) ] + d p [ 2 ] [ 1 − M ] dp[3][1] = dp[2][1] + dp[2][0,-1, -2......(2 - M)] + dp[2][1-M] dp[3][1]=dp[2][1]+dp[2][0,−1,−2......(2−M)]+dp[2][1−M]
- d p [ 3 ] [ 1 ] − d p [ 2 ] [ 1 − M ] = d p [ 2 ] [ 1 ] + d p [ 2 ] [ 0 , − 1 , − 2...... ( 2 − M ) ] dp[3][1] - dp[2][1-M] = dp[2][1] + dp[2][0,-1, -2......(2 - M)] dp[3][1]−dp[2][1−M]=dp[2][1]+dp[2][0,−1,−2......(2−M)]
然后:
- d p [ 3 ] [ 2 ] = d p [ 2 ] [ 2 ] + d p [ 2 ] [ 1 ] + d p [ 2 ] [ 0 , − 1 , − 2...... ( 2 − M ) ] dp[3][2] = dp[2][2] + dp[2][1] + dp[2][0,-1, -2......(2 - M)] dp[3][2]=dp[2][2]+dp[2][1]+dp[2][0,−1,−2......(2−M)]
从而:
- d p [ 3 ] [ 2 ] = d p [ 2 ] [ 2 ] + d p [ 3 ] [ 1 ] − d p [ 2 ] [ 1 − M ] dp[3][2] = dp[2][2] + dp[3][1] - dp[2][1-M] dp[3][2]=dp[2][2]+dp[3][1]−dp[2][1−M]
推广:
-
d
p
[
t
i
m
e
]
[
h
p
]
=
d
p
[
t
i
m
e
−
1
]
[
h
p
]
+
d
p
[
t
i
m
e
]
[
h
p
−
1
]
−
d
p
[
t
i
m
e
−
1
]
[
h
p
−
1
−
M
]
dp[time][hp] = dp[time - 1][hp] + dp[time ][hp - 1] - dp[time - 1][hp - 1 -M]
dp[time][hp]=dp[time−1][hp]+dp[time][hp−1]−dp[time−1][hp−1−M]
- h p − 1 − M > = 0 hp - 1 -M >=0 hp−1−M>=0时, d p [ t i m e − 1 ] [ h p − 1 − M ] dp[time - 1][hp - 1 -M] dp[time−1][hp−1−M]存在
- h p − 1 − M < 0 hp - 1 -M < 0 hp−1−M<0时, ( M + 1 ) t i m e − 1 (M + 1)^{time - 1} (M+1)time−1
得到:
class Solution {
public:
double KillMonster(int N, int M, int K){
std::vector<std::vector<int>> dp(K + 1, std::vector<int>(N + 1, 0));
dp[0][0] = 1; // [0 time][1....hp] = 0
for (int time = 1; time <= K; ++time) {
dp[time][0] = (long) std::pow(M + 1, time); // [1...K][0 hp] =
for (int hp = 1; hp <= N; ++hp) {
dp[time][hp] = dp[time][hp - 1] + dp[time - 1][hp];
if(hp - 1 - M >= 0){
dp[time][hp] -= dp[time - 1][hp - 1 - M];
}else{
dp[time][hp] -= std::pow(M + 1, time - 1);
}
}
}
int kill = dp[K][N];
int all = std::pow(M + 1, K);
return (double )kill/(double )all;
}
};
测试
int main()
{
std::vector<int> v = { 1,2,1,1,2,1,2 };
Solution a;
std::cout << a.KillMonster(5, 5, 5) << "\n"; //0.983796
思路:死亡概率 = 1 - 生存概率
为什么要这样:这样就可以简化画表的难度