原题截图
题意分析
原题题意分析
有个怪物,每个怪物都有血量和攻击力,玩家初始血量为,攻击力为。
- 玩家击败的第一个怪物的血量和攻击力必须都低于自己。
- 击败怪物后,玩家的血量为和攻击力为会变成他击败的那个怪物的血量和攻击力。
- 从击败的第个怪物开始,击败的第个怪物的血量和攻击力必须分别大于击败的第个怪物的血量和攻击力,即设击败的第个怪物的血量为,攻击力为,则对于所有的,有且。
问玩家最多可以击败多少个怪物。
题意补充
本题题干有一个非常大的漏洞:没有说明击败怪物的顺序。按照最常见的两种题理解——“必须从左到右击败”以及“可以按任意顺序击败”分别编写代码后,可以发现样例数据均无法通过。通过翻阅掘金平台上其他同学发布的文章,才知道本题的题意击败怪物顺序的要求是:必须从右到左击败。
解题思路
本题和一个经典的问题——最长上升子序列非常相似。最长上升子序列的解法为:设ans[i]为从1到i的子数组的最长上升子序列,n为数组长度,从1到n遍历数组,设遍历到的元素为i,对于每个i,从0到i遍历数组,设遍历到的元素为j,如果j<i,则i可以接在子数组[1,j]的最长上升子序列的后面,此时ans[i] = max(ans[i],ans[j]+1)。遍历完成后,取ans[n]的值即为答案。
而本题与最长上升子序列问题的不同是,本题中“i可以接在子数组[1,j]的最长上升子序列”的条件不是i<j,而是且,所以将最长上升子序列的代码中的i<j替换为且,即可得出本题的代码。
最后,由于本题规定击败怪物的顺序是必须从右到左击败。所以在遍历开始之前需要执行一遍reverse(h.begin(),h.end()); reverse(a.begin(),a.end());。
代码实现
#include <bits/stdc++.h>
using namespace std;
constexpr int inf=INT_MAX/2;
int solution(int n, int H, int A, vector<int> h, vector<int> a)
{
// To官方:“击败顺序是从右到左,为什么不在题干里说清楚?”
reverse(h.begin(),h.end());
reverse(a.begin(),a.end());
vector<int> ans(n,-inf);
for(int i=0;i<n;i++) if(h[i]<H && a[i]<A) ans[i] = 1;
int xmax=0;
for(int i=0;i<n;i++)
{
for(int j=0;j<i;j++)
{
if(h[j]>h[i] && a[j]>a[i])
{
ans[i] = max(ans[i],ans[j]+1);
}
}
xmax = max(xmax,ans[i]);
}
return xmax;
}
总结
本题是最长上升子序列问题的变种,因此使用的是动态规划算法,设怪物个数为n,则本题时间复杂度为,空间复杂度为。如果你有复杂度更优的解法,欢迎在下方评论。