蓝桥杯2023省b——飞机降落(dfs)以及萌新关于dfs的思考

因为不会排版所以甩个洛谷链接(?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=当前数字。而由于在生成子集时,我们不需要考虑元素的顺序,只需要考虑每个元素是否包含在子集中,所以也不像找顺序那样使用标记数组。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值