ACWing 187.导弹防御系统
题目链接:187.导弹防御系统
题目描述
为了对抗附近恶意国家的威胁,R 国更新了他们的导弹防御系统。
一套防御系统的导弹拦截高度要么一直 严格单调 上升要么一直 严格单调 下降。
例如,一套系统先后拦截了高度为 3 和高度为 4 的两发导弹,那么接下来该系统就只能拦截高度大于 4 的导弹。
给定即将袭来的一系列导弹的高度,请你求出至少需要多少套防御系统,就可以将它们全部击落。
输入格式
输入包含多组测试用例。
对于每个测试用例,第一行包含整数 n,表示来袭导弹数量。
第二行包含 n 个不同的整数,表示每个导弹的高度。
当输入测试用例 n=0 时,表示输入终止,且该用例无需处理。
输出格式
对于每个测试用例,输出一个占据一行的整数,表示所需的防御系统数量。
数据范围
1 ≤ n ≤50
输入样例
5
3 5 2 4 1
0
输出样例
2
样例解释
对于给出样例,最少需要两套防御系统。
一套击落高度为 3,4 的导弹,另一套击落高度为 5,2,1 的导弹。
题目分析
本题使用到的算法:DFS深搜 + 贪心
1.使用BFS搜索,则需要大量存储空间,存在爆空间的问题,而且无法剪枝
2.使用DFS搜索,DFS求最小值有两种方法:
(1) 使用全局变量记录最小值
(2) 迭代加深
接下来将依次用DFS搜索的两种求最小值的方法书写 Java 代码:
(1) 使用全局变量记录最小值
import java.util.*;
public class Main{
static final int N = 60;
static int n;
static int ans;
static int[] a = new int[N];
static int[] up = new int[N];
static int[] down = new int[N];
public static void dfs(int u, int su, int sd) {
// u 为当前序列点的序号, su为当前上升子序列的个数, sd为当前下降子序列的个数
//如果 su + sd >= ans 成立,则 u 一定等于 n,此处是 dfs 剪枝,没有这个逻辑的话,会导致测试超时
if (su + sd >= ans) return ; // 如果两个子序列的个数之和大于等于整个序列长度就退出搜索
if (u == n) { // 如果遍历完整个序列的话
ans = Math.min(ans, su + sd); // 就将答案更新为当前存的答案和su+sd的最小值
return ; // 然后退出搜索
}
// 情况1:用上升子序列设置系统
int k = 0; // 初始化当前搜索上升子序列序号为0
while (k < su && up[k] >= a[u]) k ++ ;
/* 如果当前搜索的点小于等于当前搜索的上升子序列末尾值且当前搜索的序列序号小于当前有的上升子序列的个数,
则搜索下一个上升子序列 */
if (k < su) { // 搜索完毕后,如果k < su则说明当前搜索的点能接在当前搜索的上升子序列的末尾
int t = up[k]; // 因为要回溯,所以需要先将更新前的up[k]备份
up[k] = a[u]; // 将当前搜索的点接在当前上升子序列的末尾(直接更新)
dfs(u + 1, su, sd); // 继续搜索原序列的下一个点
up[k] = t; // 回溯(恢复现场)
}
else { // 否则,则说明当前搜索的点无法接在当前搜索的上升子序列的末尾,则需要另开一个上升子序列
up[k] = a[u];
/* 此时因为k = su, 而k最开始是0,所有其实此时已经有了su + 1个上升子序列个数-->
此时的up[k]就是新创的一个上升子序列
再将该点接在该新创的上升子序列的末尾(直接更新)*/
dfs(u + 1, su + 1, sd); // 继续搜索下一个点,且当前拥有的上升子序列个数+1
}
// --- 同理得:
// 情况2:用下降子序列设置系统
k = 0; // 初始化当前搜索上升子序列序号为0
while (k < sd && down[k] <= a[u]) k ++ ;
/* 如果当前搜索的点大于等于当前搜索的下降子序列末尾值且当前搜索的序列序号小于当前有的下降子序列的个数,
则搜索下一个下降子序列 */
if (k < sd) { // 搜索完毕后,如果k < sd则说明当前搜索的点能接在当前搜索的下降子序列的末尾
int t = down[k]; // 因为要回溯,所以需要先将更新前的down[k]备份
down[k] = a[u]; // 将当前搜索的点接在当前下降子序列的末尾(直接更新)
dfs(u + 1, su, sd); // 继续搜索原序列的下一个点
down[k] = t; // 回溯(恢复现场)
}
else { // 否则,则说明当前搜索的点无法接在当前搜索的下降子序列的末尾,则需要另开一个下降子序列
down[k] = a[u];
/* 此时因为k = sd, 而k最开始是0,所有其实此时已经有了sd + 1个上升子序列个数-->
此时的down[k]就是新创的一个下降子序列
再将该点接在该新创的下降子序列的末尾(直接更新)*/
dfs(u + 1, su, sd + 1); // 继续搜索下一个点,且当前拥有的下降子序列个数+1
}
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
while ((n = in.nextInt()) != 0) { // 处理读入的序列个数,直到输入0为止
for (int i = 0; i < n; ++ i) a[i] = in.nextInt();
ans = n; // 初始化ans为原序列长度
dfs(0, 0, 0); // 从第一个点(序号为0),无上升和下降子序列的情况开始暴搜~
System.out.println(ans); // 输出搜索到的目标最小值ans
}
}
}
(2) 迭代加深
```java
import java.util.*;
public class Main{
static final int N = 60;
static int n;
static int[] h = new int[N];
static int[] up = new int[N];
static int[] down = new int[N];
public static boolean dfs(int depth, int u, int su, int sd){
// 如果上升序列个数 + 下降序列个数 > 总个数是上限,则回溯
if (su + sd > depth) return false;
if (u == n) return true;
// 枚举放到上升子序列中的情况
boolean flag = false;
for (int i = 1; i <= su; i ++ )
if (up[i] < h[u])
{
int t = up[i];
up[i] = h[u];
if (dfs(depth, u + 1, su, sd)) return true;
up[i] = t;
flag = true;
break; // 注意由上述证明的贪心原理,只要找到第一个可以放的序列,就可以结束循环了
}
if (!flag) // 如果不能放到任意一个序列后面,则单开一个新的序列
{
up[su + 1] = h[u];
if (dfs(depth, u + 1, su + 1, sd)) return true;
}
// 枚举放到下降子序列中的情况
flag = false;
for (int i = 1; i <= sd; i ++ )
if (down[i] > h[u])
{
int t = down[i];
down[i] = h[u];
if (dfs(depth, u + 1, su, sd)) return true;
down[i] = t;
flag = true;
break; // 注意由上述证明的贪心原理,只要找到第一个可以放的序列,就可以结束循环了
}
if (!flag) // 如果不能放到任意一个序列后面,则单开一个新的序列
{
down[sd + 1] = h[u];
if (dfs(depth, u + 1, su, sd + 1)) return true;
}
return false;
}
public static void main(String[] args){
Scanner in = new Scanner(System.in);
while ((n = in.nextInt()) != 0) {
for (int i = 0; i < n; i ++ ) h[i] = in.nextInt();
int depth = 0;
while (!dfs(depth, 0, 0, 0)) depth ++ ; // 迭代加深搜索,depth可以看作试探下开数量为 depth 个组够不够防御所有导弹,不够继续 depth++
System.out.println(depth);
}
}
}
``