因为不会排版所以甩个洛谷链接(?P9241 [蓝桥杯 2023 省 B] 飞机降落 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
思路一、贪心
1.到达时间节点+最大盘旋时长=飞机最晚撑到的时间节点,为了避免飞机等待时间太长坠机,尝试先降落最容易坠机的。
反例:
0 2 105
1 101 1
飞机1只能坚持两分钟,但是如果先降落一,降落过程太久,会把飞机2耗死,正确答案应该是2→1。
2.先降落降落用时短的。
很明显也是不行的,降落用时长的飞机也可能燃油很少,放到后面降落很容易坠落。
思路二、dp
既然在排前面的序的时候需要预留后面的情况,不能直接找出每一步最优解,那似乎可以考虑dp!
看佬题解似乎确实有dp写法,然而我是一个愉快的蒻智(状压dp是什么可以吃吗),于是
思路三、dfs
非常暴力的写法,直接例举出所有的顺序排序,只要其中一种排序能让所有飞机安全降落就可以。可以用dfs让计算机实现,思路都在注释里:
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
int N = 10;
bool st[N];
int n;
bool flag = false;
int t[N], d[N], l[N];
void dfs(int u, int last) {
if (flag) return;//只用找到一种就一直返回
if (u == n) {//最后一架飞机也降落了
flag = true;
return;
}
for (int i = 0; i < n; i++) {
if (!st[i]) {//如果当前飞机还没有降落
if (t[i] + d[i] >= last) {//如果到达加盘旋时间,意思是最多能在上空停留的时间,
//大于上一个飞机结束降落的时间 ,意思是够等到上一架飞机降落,不会坠机
st[i] = true;//可以降落
if (t[i] > last){//如果这架飞机是在上一架降落之后来的
dfs(u + 1, t[i] + l[i]);//中间可能有空时间,所以last=到达+下降
}else{ //上一架还没落就来了
dfs(u + 1, last + l[i]);//等着上一架一下落就降,中间没有空
}
st[i] = false;//回溯
} else {
return;//不够会坠机,就返回,不讨论这种顺序
}
}
}
}
int main() {
int T;
cin >> T;
while (T--) {
cin >> n;
for (int i = 0; i < n; i++){
cin >> t[i] >> d[i] >> l[i];
}
for (int i = 0; i < N; i++){
st[i] = false;
}
flag = false;
dfs(0, 0);
if (flag) cout << "YES" << endl;
else cout << "NO" << endl;
}
return 0;
}
关于dfs的思考:
最初接触dfs的时候完全不能理解计算机是怎么运行这样的递归的口牙!于是在纸上画了很多图,最终发现dfs的本质是把之前的路径保存,继续下次路径,return后在继续执行上次的路径(感觉占用了很多空间?因为之前的情况都留存着),总之画出来就像是折线图一样!
所以我们只需要写出来底层的逻辑,有点像通项公式一样,然后让计算机跑就行了。
有些dfs题解里需要再内嵌一个for循环有些没有,有些需要回溯有些不需要回溯,到底该怎么选择呢?
经过观察发现:需要回溯的都是外部变量,不会随着递归自动更新的变量,需要手动更新;而在dfs函数内部的变量,也会跟随函数自动返回到上一个状态。
内嵌的for循环,或许是搜索的元素有顺序(比如在连续的数组里)的情况。
比如飞机降落这题,其实就是在找一个数列的不同排列顺序。可以抽象成有一个数列{1,2,3},例举所有的排列顺序{123}{132}{213}{231}{312}{321}。
第一层递归决定第一个数字是几,从1开始遍历;第二层依旧从1开始遍历,但1已经在此条路径中被使用过,所以跳过1存为2;第三层,由于1,2都被使用过,第三个数字存为3。
第三层遍历已经到达数组的尽头,递归结束,返回第二层,此时for循环里的i++(原本等于2),自增为3,进入第三层递归。
但是!此时的数字2,已经没有被使用过了,目前的排列顺序是{1,3,},第三层递归依旧从int i=1开始,1已经被使用,但是2没有,所以第三个数字填为2。
综上,需要解决的两点:如何给当前路径里已经用过的数字标记?如何在更新路径时,把数字的标记擦掉?
1开个标记数组就能解决,而2就需要回溯状态。在代码里,st就是标记数组,st[i] = false;//回溯 ,这一句就是回溯状态。
找出数列元素的所有排列顺序,还类似一种情况,找出所有数列元素的子集。
子集的长度不一定等于原数列,所以不需要把空位都填满。所以和找排列顺序的题目只有一点不同:每次重新开始递归,不用再从int i=1开始遍历,而是int i=当前数字。而由于在生成子集时,我们不需要考虑元素的顺序,只需要考虑每个元素是否包含在子集中,所以也不像找顺序那样使用标记数组。