动态规划题——fate
Fate
最近xhd正在玩一款叫做FATE的游戏,为了得到极品装备,xhd在不停的杀怪做任务。久而久之xhd开始对杀怪产生的厌恶感,但又不得不通过杀怪来升完这最后一级。现在的问题是,xhd升掉最后一级还需n的经验值,xhd还留有m的忍耐度,每杀一个怪xhd会得到相应的经验,并减掉相应的忍耐度。当忍耐度降到0或者0以下时,xhd就不会玩这游戏。xhd还说了他最多只杀s只怪。请问他能升掉这最后一级吗?
输入格式:
输入数据有多组,对于每组数据第一行输入n,m,k,s(0 < n,m,k,s < 100)四个正整数。分别表示还需的经验值,保留的忍耐度,怪的种数和最多的杀怪数。接下来输入k行数据。每行数据输入两个正整数a,b(0 < a,b < 20);分别表示杀掉一只这种怪xhd会得到的经验值和会减掉的忍耐度。(每种怪都有无数个)
输出格式:
输出升完这级还能保留的最大忍耐度,如果无法升完这级输出-1。
输入样例:
10 10 1 10 |
输出样例:
0 |
我的题解:
|
思考:
问题背景
- 你需要获得一定的经验值(
n
)。 - 你有一定的忍耐度(
m
),每次杀怪都会消耗一部分。 - 怪物有多种(
k
种),每种怪物提供不同的经验值和消耗不同的忍耐度。 - 你最多可以杀
s
个怪物。
此处我们会发现问题的约束有点多,一维数组已经满足不了我们。
动态规划的思路
- 定义状态:
dp[i][j]
表示你还需要i
点经验,且杀了j
个怪物时的剩余最大忍耐度。 - 初始化状态: 初始时,你需要
n
点经验,且未杀任何怪物,因此dp[n][0] = m
。 - 状态转移:
- 遍历每一种怪物。
- 对于每种怪物,尝试杀
0
到s-1
个,查看不同数量下的剩余忍耐度和所需经验。 - 对每个杀怪数量,检查如果杀了这个怪物后,是否能得到更好的剩余忍耐度(即
dp[next_exp][j+1]
)。 - 转移方程:如果杀死一个怪物后剩余忍耐度更高,则更新
dp[next_exp][j+1]
。
- 寻找结果: 在所有经验值为
0
(即达到目标经验值)的情况中,找到忍耐度最大的那个。
首先看main函数,这是我们解题的主要步骤:
vector<pair<int, int>> monsters(k); |
创建一个mosters数对,代表怪物的种类存储结构。有k种怪兽,而每种怪兽会给出两个正整数a,b(0 < a,b < 20);分别表示杀掉一只这种怪会得到的==经验值==和==会减掉的忍耐度==。注意此处,我们最终的忍耐度是看剩余的忍耐度,而经验值是看总共的经验值,一个是消耗,一个是增加,而目标是得到忍耐度的最大值。那么我们首先会想到尽量少减掉忍耐度,尽量增加更多的经验。
if (result >= 0) { |
这里也很简单。调用一个函数获得忍耐度的最大值,如果不存在,将会返回负值;如果存在,输出即可。
接下来看到solve函数,真正的逻辑核心。
int solve(int n, int m, int k, int s, vector<pair<int, int>>& monsters) |
数组我们一般引用它地址
vector dp(n + 1, vector(s + 1, -1000));//还需的经验值,杀怪数,剩余的忍耐度,-1000代表不存在 |
初始化的时候记得设置动态规划的数组,其值是目标结果,而初始状态需要赋有意义的值。
int exp = monsters[i].first, cost = monsters[i].second; |
采用first和second代替a和b,方便自己思考记住。这个看个人习惯,就是说我们不需要增加设置数据结构的环节,其实是这样。
for (int i = 0; i < k; ++i) {//遍历所有的怪 |
最后解释真正重要的部分。
我们外层遍历所有种类的怪,而内遍历杀所有个数,就可以考虑到所有种类,每种个数杀任意只得所有情况了,而实际得到的dp值是可以实时更新的。
最里面一层for,注释里已经解释了exp为什么从前往后遍历,我们可以自己验证一下,假设第一只怪数值是(1,1),第一次遍历的时候发现dp[n][0]
处有一个m,那么我们考虑next_exp就是m-1,dp[n-1][1]
表示杀了一只怪之后还差n-1点经验,即增加了一点经验,之后的忍耐值。可想而知,它是下降的,那么我们比较杀了这只怪之后的忍耐度会不会提高,如果没有提高,我们不更新忍耐值,即并不杀怪。
int result = -1; |
最后我们看的是dp[0][j]
,就是不差经验,的时候,各种杀怪情况留下最大的忍耐度。
动态规划还挺抽象的,有必要多看几遍。