P4408 逃学的小孩-洛谷-java题解-树的直径

本文详细解析了NOI2003逃学的小孩问题,通过两次深度优先搜索(DFS)找到树的直径,并利用树形DP优化算法,最终求得任意两点间的最短路径,实现效率最大化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传送门:P4408 [NOI2003] 逃学的小孩 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P4408


        题目里千万不要漏了这句话:任两个居住点间有且仅有一条通路!!!!这个条件让我们后面方便求任意一个点到其他点的距离。假设我们已经确定P,Q点为直径端点,那么PQ是必走的,CP,CQ会选取其中小的一段走,所以我们的C点要满足min(CQ,CP)最大,这样就可以使答案最大。min(CP,CQ)最大的意思是,假设我们用两次dfs求得某条直径的两个端点P,Q,那么我们枚举点C,则ans=max(PQ+min(CP,CQ))。直径好求,直接套模板。因为要求端点,所以用两次搜索。然后就是求min(CP,CQ),我们可以稍微改一下树上dp求直径的模板:(下面这个我不好解释,大伙可以自己模拟一组简单的数据,就知道什么意思了)

static void dfs2(int u,int fa){
        List<Edge> edges = tree.get(u);
        for(Edge edge : edges){
            if(fa==edge.v)continue;
            dis[edge.v]=dis[edge.u]+edge.w;
            dfs2(edge.v,u);
        }
    }

原整代码(含解析):

import java.io.*;
import java.util.*;

class Edge{
      int u;
      int v;
      int w;
      public Edge(int u,int v,int w){
          this.u=u;
          this.v=v;
          this.w=w;
      }
}
/*如果有小伙伴对树的直径模板(两次搜索和dp)不熟,建议先去把模板弄熟)*/
class Main {
    static int n,m;
    static long[] dis,disp,disq;
    static boolean[] vis;
    static List<List<Edge>> tree;
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StreamTokenizer st = new StreamTokenizer(br);
        st.nextToken();n=(int)st.nval;
        st.nextToken();m=(int)st.nval;
        dis=new long[n+1];
        disp=new long[n+1];
        disq=new long[n+1];
        vis=new boolean[n+1];
        tree=new ArrayList<>();
        for(int i=0;i<=n;i++){
            tree.add(new ArrayList<Edge>());
        }
        //以上为数据输入与初始化
        int u,v,w;
        for(int i=1;i<=m;i++){
            st.nextToken();u=(int)st.nval;
            st.nextToken();v=(int)st.nval;
            st.nextToken();w=(int)st.nval;
            tree.get(u).add(new Edge(u,v,w));
            tree.get(v).add(new Edge(v,u,w));
        }
        int P=0,Q=0;        //P,Q为直径两个端点
        long ans=0,max=0;
        vis[1]=true;dfs(1); //vis[1]=true是个好习惯
        for(int i=1;i<=n;i++){
            if(dis[i]>max){
                P=i;
                max=dis[i];
            }
            dis[i]=0;
            vis[i]=false;
        }
        vis[P]=true;dfs(P);//vis[P]是个好习惯
        for(int i=1;i<=n;i++){
            if(dis[i]>ans){
                Q=i;
                ans=dis[i];
            }
            dis[i]=0;
        }
        dis=disp;dfs2(P,0);//求出P点到每一个点的距离
        dis=disq;dfs2(Q,0);
        long anss=0;
        for(int i=1;i<=n;i++){
                if(i==P||i==Q)continue;
                long temp=ans+Math.min(disp[i],disq[i]);
                anss=anss>temp?anss:temp;
        }
        System.out.println(anss);
    }
    static void dfs(int k) {                //标准两次dfs模板
        List<Edge> edges = tree.get(k); 
        int len=edges.size();
        for(int i=0;i<len;i++){
            int to=edges.get(i).v;
            if(!vis[to]){
                vis[to]=true;
                dis[to]=edges.get(i).w+dis[k];
                dfs(to);
            }
        }
    }
    static void dfs2(int u,int fa){        //大家自己模拟一组简单的数据就能明白了
        List<Edge> edges = tree.get(u);
        for(Edge edge : edges){
            if(fa==edge.v)continue;
            dis[edge.v]=dis[edge.u]+edge.w;
            dfs2(edge.v,u);
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

玛卡左家陇分卡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值