题目概述
小E需要在一个按顺序出现的怪物序列中选择击败怪物,要求满足:
- 每个被击败的怪物血量和攻击力都严格小于小E当前属性
- 被击败的怪物序列中,后一个怪物的血量和攻击力必须严格大于前一个
输入:怪物数量n,初始血量H,初始攻击A,怪物血量数组h,攻击数组a
输出:最多能击败的怪物数量
问题分析
关键约束点:
- 双条件筛选:怪物必须同时满足h[i]<当前H且a[i]<当前A才能被选择
- 严格递增序列:后选怪物的h和a必须都>前一个的h和a
- 顺序处理:怪物按出现顺序处理,但可以跳过任意个
解决思路
分步策略
- 预处理过滤:先筛选出所有可能击败的怪物(h<初始H且a<初始A)
- 转换为LIS问题:在有效怪物中寻找最长的严格递增子序列(按h和a同时递增)
- 动态规划求解:使用经典LIS解法处理双维度的递增条件
算法选择
-
动态规划:时间复杂度O(n²),适用于n<100的数据规模
-
状态定义:dp[i]表示以第i个怪物结尾的最长序列长度
-
转移方程:
dp[i] = max(dp[j] + 1) ∀j < i且h[i]>h[j]且a[i]>a[j]
代码实现
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class MonsterHunter {
public static int maxMonsters(int n, int H, int A, int[] h, int[] a) {
// Step1: 预处理有效怪物
List<int[]> valid = new ArrayList<>();
for (int i = 0; i < n; i++) {
if (h[i] < H && a[i] < A) {
valid.add(new int[]{h[i], a[i]});
}
}
if (valid.isEmpty()) return 0;
// Step2: 动态规划求解最长递增子序列
int[] dp = new int[valid.size()];
Arrays.fill(dp, 1);
int max = 1;
for (int i = 0; i < valid.size(); i++) {
for (int j = 0; j < i; j++) {
int[] cur = valid.get(i);
int[] pre = valid.get(j);
if (cur[0] > pre[0] && cur[1] > pre[1]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
max = Math.max(max, dp[i]);
}
return max;
}
}
复杂度分析
-
时间复杂度:O(n²)
- 预处理阶段:O(n)
- 动态规划阶段:双重循环O(m²),其中m为有效怪物数量(m ≤ n)
-
空间复杂度:O(n)
- 存储有效怪物列表:O(n)
- DP数组:O(n)
关键点解析
- 预处理的重要性:直接过滤掉不可能击败的怪物,避免无效计算
- 双维度比较:必须同时比较血量和攻击力两个维度
- 严格递增要求:注意是严格大于(>),不是大于等于
- 初始属性不变:题目中击败怪物后小E的属性不会变化(易错点!)
测试样例验证
样例1
输入:
n=3, H=4, A=5
h=[1,2,3], a=[3,2,1]
处理流程:
-
有效怪物:[所有三个都满足h<4且a<5]
-
寻找严格递增序列:
- [1,3] -> 无法找到后续更大的
- [2,2] -> 无法找到后续更大的
- [3,1] -> 无法找到后续更大的
输出:1
样例3
输入:
n=4, H=20, A=25
h=[10,15,18,22], a=[12,18,20,26]
处理流程:
- 有效怪物:前三项(22的a=26 >25被过滤)
- 最长序列:10/12 →15/18 →18/20
输出:3
总结
本题的解题关键在于将原问题转化为二维LIS问题。通过以下步骤保证正确性:
- 预处理过滤不可击败的怪物
- 在有效怪物序列中寻找最长的双严格递增子序列
- 使用标准LIS解法处理二维条件
扩展思考:如果小E击败怪物后属性会变化(如增加),问题将变得复杂得多,可能需要使用完全不同的贪心策略或状态压缩DP来解决。