(PTA)L2-3:龙龙送外卖

L2-3 龙龙送外卖  (分数 25)

龙龙是“饱了呀”外卖软件的注册骑手,负责送帕特小区的外卖。帕特小区的构造非常特别,都是双向道路且没有构成环 —— 你可以简单地认为小区的路构成了一棵树,根结点是外卖站,树上的结点就是要送餐的地址。

每到中午 12 点,帕特小区就进入了点餐高峰。一开始,只有一两个地方点外卖,龙龙简单就送好了;但随着大数据的分析,龙龙被派了更多的单子,也就送得越来越累……

看着一大堆订单,龙龙想知道,从外卖站出发,访问所有点了外卖的地方至少一次(这样才能把外卖送到)所需的最短路程的距离到底是多少?每次新增一个点外卖的地址,他就想估算一遍整体工作量,这样他就可以搞明白新增一个地址给他带来了多少负担。

输入格式:

输入第一行是两个数 N 和 M (2≤N≤105, 1≤M≤105),分别对应树上节点的个数(包括外卖站),以及新增的送餐地址的个数。

接下来首先是一行 N 个数,第 i 个数表示第 i 个点的双亲节点的编号。节点编号从 1 到 N,外卖站的双亲编号定义为 −1。

接下来有 M 行,每行给出一个新增的送餐地点的编号 Xi​。保证送餐地点中不会有外卖站,但地点有可能会重复。

为了方便计算,我们可以假设龙龙一开始一个地址的外卖都不用送,两个相邻的地点之间的路径长度统一设为 1,且从外卖站出发可以访问到所有地点。

注意:所有送餐地址可以按任意顺序访问,且完成送餐后无需返回外卖站

输出格式:

对于每个新增的地点,在一行内输出题目需要求的最短路程的距离。

输入样例:

7 4
-1 1 1 1 2 2 3
5
6
2
4

输出样例:

2
4
4
6

思路  本题主要考察的是 树结构+记忆化搜索+贪心。首先 需要知道我们要求的是什么,我们要求的是送完所有餐时的最短路程,题目允许送餐后无需返回外卖站。我们假设送完所有餐后返回外卖站,列出一些送完餐返回外卖站的情况,发现如果送完餐返回外卖站,不管送餐顺序如何,送餐所花费的距离是一样的。并且,我们可以很容易发现这个距离的大小就是此时送餐路线的连通图距离*2。于是根据这一结论,我们得到唯一影响送餐距离的不是送餐顺序而是完成送餐后的位置。根据贪心思路,最后我们可以知道,我们要求的值=目前送餐路线的树的边大小*2 - 送餐路线的树中最深的一个结点到根的距离。

目前送餐路线的树的边大小 这个值是根据每次新增的送餐点来变化的,起始时我们令它的大小为ans=0,我们用一个bool型的数组b来记录每个送餐点的访问情况(外卖点的位置起始时就赋值为true)。(其实可以发现已经被访问的送餐点就是一个联通图),我们要计算新加入的送餐点到我们已经访问过的联通图的距离len,然后用ans+=len*2。

送餐路线的树中最深的一个结点到根的距离 起始时令它为maxlen=0,这里的计算需要用到记忆化搜索,不然可能会爆时间复杂度。用记忆化搜索来对新增的结点到根结点的距离进行计算,如果大于maxlen。则对maxlen进行更新。

具体见AC代码(欢迎提问):

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
#define pii pair<int,int> 
#define inf 0x3f3f3f3f
#define f first
#define s second
#define mii map<int,int>
int p[100010];
int l[100010];
int n,m;
bool b[100010];//用来记录这个点有没有被访问过
//最后减掉最深的一条边即可
//每次需要加上的就是新的点 到 最近的已经访问过的边的距离
int dfs(int x){
    if(l[x]!=0) return l[x];  //记忆化搜索
    if(p[x]!=-1) l[x]=dfs(p[x])+1; //直接记忆化一波
    return l[x];
}

signed main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>p[i];
        if(p[i]==-1) b[i]=true;
    }
    int maxlen=0;//最大的一条边
    int ans=0;//用来记录已有的边的大小
    while(m--){
        int x;
        cin>>x;
        maxlen=max(maxlen,dfs(x));//找最大的边
        int len=0;//记录需要加入的长度
        while(!b[x]){  //如果这个点没有被访问过,说明需要加入新的边
            len++;
            b[x]=true;
            x=p[x];   //直到找到已经访问过的结点
        }
        ans+=len*2;
        cout<<ans-maxlen<<endl;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值