L2-043 龙龙送外卖(个人解析)

本文讲述了在L2-043龙龙送外卖的编程题目中,如何利用pre[]和nex[]数组构建树结构,结合深度优先搜索(BFS)和折返策略来解决树的最短路径问题,强调了预处理节点深度和回溯的重要性。
摘要由CSDN通过智能技术生成

L2-043 龙龙送外卖 - 团体程序设计天梯赛-练习集 (pintia.cn)

求求你了,这道题也太妙了吧。

按照我的原始人思路,第一次遇见这道题的时候,会这样想:这是一个树,而且是以双亲的形式建立的,那么就应该 pre[ ] 数组这样存储,类似并查集。然后我就想,那应该是和深度有关吧?先走浅的,再走深的。那么至少应该知道每一个点的深度吧?所以层序遍历是少不了的吧?然后我就走一步看一步,先想把BFS写了。写的过程中发现,嗯?我怎么遍历下一层啊?我只知道这个点的双亲结点啊?没办法了,只能看看题解,也就发现的第一个妙点:pre[ ]+nex[ ] 存储树。集合了并查集和二叉树结构体的特点,非常完美的构造出了一个可以向上向下双向遍历的高级结构。pre数组可以保证树的向上遍历,而nex则可以保证树向下遍历。其实如果使用图 g[ ] [ ] 也许也能达到同样的效果,下次可以自己试试。

然后就是核心求解路径的思路:默认返回到上一个分岔路口。我是真的想不到这一点,默认他要返回。因为在接新的订单的时候,就是很可能需要折返,所以我们先默认他折返,回到上一个分岔路口,将这个地方默认为新的起点(我们假设位置为a)。然后,精髓来了,现在你想要加入一个新的订单(下面称之为b),如果这个订单在a的另一个子树中,我们进行求preNode操作会将 preNode=a ,那么我们就刚刚好从这个a点出发;反之,如果这个订单位置就在之前遍历过的a点的子树中,那么我们之后求解preNode,会得到 preNode=a.son ,这里又可以分为两个情况,一个是这个 a.son 刚好就是本次我们想要算的(也就是之前已经走过了的),那么此时计算距离=(depth[node] - depth[preNode]) * 2=0(因为此时preNode=b,node=b,深度相同)。第二种情况,那就是在这个子树中没有遍历过的位置,我们更新的距离仍然是到 a.son到b的一个来回。

可以看看图,你会发现,我们这一趟下来,最终都是会回到起点a,而这个起点a的之前的起点-->c-->d……最后会回到根节点,而我们最后真正重复计算的地方,其实就是一个最大深度!!!

本题你可以学到的:pre[ ]+nex[ ] 存储树、折返树+最短距离

#include <iostream>
#include <vector>
#include <queue>
using namespace std;
//树的最短路径问题,而且是会重复遍历的那种。
//先遍历?但是怎么处理呢?先从浅的开始吧?层序遍历?计算需要遍历的点的深度,优先遍历浅的那边
//对的,大体思路和我想的差不多,最长的最后走:每一次送达之后都 sum+=路径*2,表示要回去,最后用sum-maxdeep,表示最后走的一段不用回去
//而且肯定是要层序遍历的,而且没有必要深究顺序问题,只需要抓住当前深度即可
const int N = 1e5 + 2;
int maxLen = 0;
int curLen = 0;	//当期路径长度
vector<int>pre(N);	//上一个
vector<int>nex[N];	//下一个
vector<int>depth(N);
vector<int>book(N);	//登记
int n, m;

struct Node
{
	int num;
	int deep;
};

void BFS(int s)	//这里只是用来记录每一个点的深度,方便计算
{
	queue<Node>que;
	que.push({s,0});
	int size = 0;
	while (!que.empty())
	{
		size = que.size();
		while (size--)
		{
			Node cur = que.front();
			que.pop();
			
			depth[cur.num] = cur.deep;	//记录深度

			for (int it : nex[cur.num])	//遍历当前点的下一个
				que.push({ it,cur.deep+1 });
		}
	}
}

int main()
{
	cin >> n >> m;
	int node;
	int start = 0;
	for (int i = 1; i <= n; i++)
	{
		cin >> node;
		pre[i] = node;
		nex[node].push_back(i);
		if (node == -1)	start = i;	//记录外卖站
	}

	BFS(start);	//标记各点深度

	book[start] = 1;
	while (m--)
	{
		cin >> node;
		//先找到最近的,已经走过的,靠近起点的 点
		//为什么要知道这里呢?因为这里之所以走过:1、起点  2、某一次的终点(最近)  3、某一次的偏转点(最近)
		//看得出来,这个点是计算距离的关键,因为从这里开始才能一口气到达终点,我们也是用这个点来 *2
		//*2之后,就可以理解为,上一次送完之后,我又回到了preNode这个位置,相当于是下一次的起点!!!
		//所以计算下一次的路径的时候,就直接从 preNode 出发,计算的就是直线距离
		int preNode = node;
		while (!book[preNode])	//直到没被标记,一直向上迭代
		{
			book[preNode] = 1;	//记录
			preNode = pre[preNode];
		}
		maxLen = max(maxLen, depth[node]);	//更新最大深度(注意!是深度最大的最后走,而不是跨度最大的最后走)
		curLen += (depth[node] - depth[preNode]) * 2;
		cout << curLen - maxLen << endl;
	}

	return  0;
}

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值