团体天梯练习 L2-043 龙龙送外卖
L2-043 龙龙送外卖
龙龙是“饱了呀”外卖软件的注册骑手,负责送帕特小区的外卖。帕特小区的构造非常特别,都是双向道路且没有构成环 —— 你可以简单地认为小区的路构成了一棵树,根结点是外卖站,树上的结点就是要送餐的地址。
每到中午 12 点,帕特小区就进入了点餐高峰。一开始,只有一两个地方点外卖,龙龙简单就送好了;但随着大数据的分析,龙龙被派了更多的单子,也就送得越来越累……
看着一大堆订单,龙龙想知道,从外卖站出发,访问所有点了外卖的地方至少一次(这样才能把外卖送到)所需的最短路程的距离到底是多少?每次新增一个点外卖的地址,他就想估算一遍整体工作量,这样他就可以搞明白新增一个地址给他带来了多少负担。
输入格式:
输入第一行是两个数 \(N\) 和 \(M\) ( \(2≤N≤10^{5}\), \(1≤M≤10^{5}\) ),分别对应树上节点的个数(包括外卖站),以及新增的送餐地址的个数。
接下来首先是一行 \(N\) 个数,第 \(i\) 个数表示第 \(i\) 个点的双亲节点的编号。节点编号从 \(1\) 到 \(N\) ,外卖站的双亲编号定义为 \(−1\) 。
接下来有 \(M\) 行,每行给出一个新增的送餐地点的编号 \(X_{i}\) 。保证送餐地点中不会有外卖站,但地点有可能会重复。
为了方便计算,我们可以假设龙龙一开始一个地址的外卖都不用送,两个相邻的地点之间的路径长度统一设为 \(1\) ,且从外卖站出发可以访问到所有地点。
注意:所有送餐地址可以按任意顺序访问,且完成送餐后无需返回外卖站。
输出格式:
对于每个新增的地点,在一行内输出题目需要求的最短路程的距离。
输入样例:
7 4
-1 1 1 1 2 2 3
5
6
2
4
输出样例:
2
4
4
6
解题思路
这道题还是比较难的,关键在于如何确定这个最短路径距离。
首先这是一个树的结构,题目中也说了。每多出现一个点,需要重新规划从根节点开始到所有需要到达的点的最短距离,当然每个点的访问顺序可以是乱序的,只要保证都到达了即可。对于所有需要到达的点,其连到父节点的边一定是要经历的,也就是说假如有一条边一个端点为 \(v\) ,其父节点也就是这条边的另一条端点 \(u\),那么如果 \(v\) 是需要到达的节点之一,这条 \(u->v\) 的边是一定需要经过的,其原因是这是一个树的结构,到达 \(v\) 的路径一定只能经过其父节点到达。
根据这一点,而且从根节点到达子节点路径是唯一的,所以所有从根节点到某一子节点上路径涉及到的边一定都需要经历,且由于只能从根节点出发,所以有很多边需要经历两次。但问题在于,最短路径距离如何确定,有哪些边只需要经历一次,有哪些边需要经历两次。
不妨逆向思维考虑这个问题,题中说从根节点出发但是不必要回到根节点。我们可以先将所有涉及到的边都经历两次,算出此时的路径距离。由于我们可以不用返回根节点,而所有涉及的边都经历两次,必定有一条从根节点到某一子节点的路径是多余的。此时,我们可以采取贪心的策略,因为要求最短路径距离,所以应当选取最长的一条从根节点到当前需要到达的子节点的距离,将这个距离减去,那么最终得到的就是当前的最短路径距离。
所以最后,我们的问题转换为,累加所有涉及的边两倍的边权(边权为1,所以每次累加2),然后减去一条最长的从根节点到需要到达的子节点的距离,这个最长距离随着需要到达的点的增多而更新。
分析完了这个关键问题,剩下的主要就是如何求解这个问题。
首先要建树,并且确定根节点,我自己使用的是链式前向星,当然也可以用\(vector\)邻接表。然后第一要事是求出根节点到其它各点的路径长度,可以用 \(dfs\) 自顶向下求这个距离,应该也可以用 \(bfs\) 求点的层次一样求这个距离,两者应该都行,而且我们不需要记录其父节点或者判断某个点是否被遍历过,因为这是一个树的结构,每个点只会被遍历一次。
建图的同时,还需要记录每个节点的父节点,这主要是为了后期计算答案需要用到。在计算答案时,每多出现一个需要到达的节点,都需要更新此时的最长路径距离,然后累加所有 未被累加过 的边两倍的边权,注意是 未被累加过的, 不然会造成重复累加导致错误。如果在累加过程中某个节点 \(v\) 的父节点 \(u\) 作为下端点的边发现已经被累加过了,那么也可以直接退出循环,这是因为如果 \(v\) 的父节点 \(u\) 已经被访问过,那么从根节点到达这个 \(u\) 的路径一定已经被累加到路径距离中了,所以此时可以在累加了 \(u->v\) 后,直接退出循环;如果根节点到 \(v\) 路径上所有边没被累加过,需要从 \(v\) 开始一直向上迭代,直到根节点,累加所有边两倍的边权。
/* 一切都是命运石之门的选择 El Psy Kongroo */
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<vector>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<cmath>
#include<functional>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<int, pii> piii;
typedef pair<double, double> pdd;
typedef pair<string, int> psi;
//typedef __int128 int128;
#define PI acos(-1.0)
#define x first
#define y second
//int dx[4] = {1, -1, 0, 0};
//int dy[4] = {0, 0, 1, -1};
const int inf = 0x3f3f3f3f, mod = 1e9 + 7;
const int N = 1e5 + 10;
int h[N], e[N], ne[N], idx; //链式前向星
int fa[N]; //fa[v]表示v的父节点
int dist[N]; //dist[v]表示根节点到v的距离
bool st[N]; //记录当前点是否已经到达过
int n, m, root;
void add(int a, int b){
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
//深度优先搜索根节点到各个节点路径距离
void dfs(int u){
for(int i = h[u]; ~i; i = ne[i]){
int v = e[i];
dist[v] = dist[u] + 1; //自顶向下
dfs(v);
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
memset(h, -1, sizeof(h));
cin >> n >> m;
for(int v = 1; v <= n; v ++ ){
cin >> fa[v];
if(fa[v] == -1){
root = v;
continue;
}
int u = fa[v];
add(u, v);
}
dfs(root); //dfs求根节点到各点距离 (应该也可用BFS或其它)
int ans = 0, maxd = 0;
while(m -- ){
int v; cin >> v;
maxd = max(maxd, dist[v]); //更新最长路径
while(!st[v] && v != root){
st[v] = true;
ans += 2; //每个没经过的点,其父节点到这个点这条边经过两次
v = fa[v];
}
cout << ans - maxd << endl; //因为可以不回去 所以减去最长的路径
}
return 0;
}
一切都是命运石之门的选择,本文章来源于博客园,作者:MarisaMagic,出处:https://www.cnblogs.com/MarisaMagic/p/17338796.html,未经允许严禁转载