前言
大家好,我是spring小子!这两天抽时间(说是抽时间,看起来自己很忙,实际上贪玩去了,一天要玩好几个小时哈哈哈)研究了一下DFS(深度优先搜索算法),经过查看多个视频和多篇文章,终于算是搞清楚了。然后看别人的代码,通过问语言大模型来帮助自己理解,结果它们有时候胡言乱语,搞得我越来越迷。最后把别人的代码放IDEA里,通过打断点debug,然后自己画个图,反反复复,最终搞明白了代码的整体逻辑。
问题描述
N架飞机准备降落到某个只有一条跑道的机场。其中第i架飞机在Ti时刻到达机场上空,到达时它的剩余油料还可以继续盘旋 Di个单位时间,即它最早可以于Ti时刻开始降落,最晚可以于Ti+Di时刻开始降落。降落过程需要 Li个单位时间。
一架飞机降落完毕时,另一架飞机可以立即在同一时刻开始降落,但是不能在前一架飞机完成降落前开始降落。
请你判断N架飞机是否可以全部安全降落。
输入格式
输入包含多组数据。
第一行包含一个整数 T,代表测试数据的组数。
对于每组数据,第一行包含一个整数 N。
以下 N 行,每行包含三个整数:Ti,Di 和 Li。
输出格式
对于每组数据,输出 YES 或者 NO,代表是否可以全部安全降落。
样例输入
2
3
0 100 10
10 10 10
0 2 20
3
0 10 20
10 10 20
20 10 20
样例输出
YES
NO
样例说明
对于第一组数据,可以安排第 3架飞机于 0时刻开始降落,20时刻完成降落。安排第 2 架飞机于 20 时刻开始降落,30时刻完成降落。安排第 1架飞机于 30时刻开始降落,40时刻完成降落。
对于第二组数据,无论如何安排,都会有飞机不能及时降落。
思路分析
这道题说实话,大体的思路就是一个暴力枚举:和我们手动判断的过程一样,就是列举每一种可能的情况,然后看看是否存在有符合题意的方案。如果有就输出YES,否则输出NO。
听起来是不是很简单了,没错,大方针确实简单,但我觉得难点就在于,采用DFS来遍历。如果单说DFS的话,好像也不难哦。下面我就以三架飞机为例,来简单讲一下DFS的思想。
如图,我们要先从三架飞机中选择任意一架来降落,然后再从剩余的两架飞机中选择任意一架来降落,最后选择剩余的一架来降落。
注意,这里我们先不考虑能否降落成功,那么是不是就该这样选择?
好,理解了这个之后,那我们就列举出所有的情况,就是这张图。接下来就一步步地遍历。
比如我们先走下图这条线
首先尝试降落飞机1,如果能降落,那就继续看飞机2能否降落,如果可以,就继续往下看飞机3。假如飞机3不能降落,那这条线就行不通了,那我们就“原路返回”,回到飞机2,将其标记为没降落,再回到飞机2,标记为未降落。再回到起点,开始下一条线。最终我们就可以列举出所有的情况,如果某一种可行,那么就做一个标记,然后输出YES就可以了。
这就是DFS,即:
从图中一个未访问的顶点 V 开始,沿着一条路一直走到底,然后从这条路尽头的节点回退到上一个节点,再从另一条路开始走到底,不断递归重复此过程,直到所有的顶点都遍历完成,它的特点是不撞南墙不回头,先走完一条路,再换一条路继续走。
接下来我们讲一下,飞机在何种情况下能够降落。
根据题意,飞机最长等待时间为Ti+Di,如果当前时间比飞机最长等待时间要大,也就是说飞机等不到了,那就不能安全降落;反之则可以降落。能降落分两种情况:
① 跑道空闲,即当前时间小于飞机到达时间,那就直接降落;
②跑道被占用,那么就等待空闲再降落。
这里的“当前时间”其实也可以理解为跑道空闲时的时间,总之就是一个time变量,拿它来与相关数据做比较的。
我们说DFS是“不撞南墙不回头”,本题的“南墙”有两个:一是走到图的末尾,也就是所有飞机都遍历完了;二是当前线路中途就碰到飞机不能降落,那就直接开始下一条路线了。
下面是具体的代码,有详细的注释,大体就是递归:递进和回溯。参考的代码是用的基本变量类型,既然我们是java选手,那就把面向对象用起来!这里我是将每一个飞机的相应数据封装成了一个类,感觉这样数据会工整一些。当然你也可以试试其他方式,这个都不重要,最重要的就是DFS代码的具体实现。
代码实现
import java.util.Scanner;
// 1:无需package
// 2: 类名必须Main, 不可修改
//题目地址:https://www.lanqiao.cn/problems/3511/learning/
public class Main {
//输入数据组数
private static int T;
//每组有几架飞机
private static int N;
private static boolean flag = false;
private static Plane[] planes;
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
//在此输入您的代码...
T = scan.nextInt();
//对于每一组数据
while (T-- > 0) {
N = scan.nextInt();
planes = new Plane[N];
for (int i = 0; i < N; i++) {
int ti = scan.nextInt();
int di = scan.nextInt();
int li = scan.nextInt();
planes[i] = new Plane(ti, di, li);
}
//先测试输入有效还是无效:测试结果为有效
/*for (int i = 0; i < N; i++) {
System.out.println("第"+(i+1)+"架飞机的数据:"+planes[i].getTi()+" "+planes[i].getDi()+" "+planes[i].getLi());
}*/
//有了数据就开始dfs遍历
dfs(0, 0);
if (flag) {
System.out.println("YES");
} else System.out.println("NO");
//清空数据,方便下一组数据
flag = false;
}
scan.close();
}
/**
* @param number 当前降落了的飞机数量
* @param time 当前时间
*/
private static void dfs(int number, int time) {
//先写终止条件
//如果已经降落的飞机数量大于等于已有飞机数量,也就是全都降落完了,就返回去
if (number >= N) {
flag = true;
return;
}
//然后就写具体的向下遍历的过程
//有多少个选择就几层循环
for (int i = 0; i < N; i++) {
//如果当前飞机没降落,
if (!planes[i].islanded) {
//那就判断一下能否降落:当前时间要小于等于飞机最长等待时间
if (time <= planes[i].getTi() + planes[i].getDi()) {
planes[i].setIslanded(true);
//两种降落情况:①跑道空闲,直接降落
if (time < planes[i].getTi()) {
dfs(number + 1, planes[i].getTi() + planes[i].getLi());
}
//飞机在等跑道空闲,那空闲之后就直接降落
else {
dfs(number + 1, time + planes[i].getLi());
}
//下面的遍历完了,回来之后,将自己标为没降落,尝试下一个方案
planes[i].setIslanded(false);
}
}
}
}
static class Plane {
//飞机到达时间
private int Ti;
//飞机最大盘旋时间
private int Di;
//飞机降落需要的时间
private int Li;
//飞机是否降落了
private boolean islanded;
public Plane(int ti, int di, int li) {
Ti = ti;
Di = di;
Li = li;
}
public int getTi() {
return Ti;
}
public void setIslanded(boolean islanded) {
this.islanded = islanded;
}
public int getDi() {
return Di;
}
public int getLi() {
return Li;
}
}
}