蓝桥杯-大臣的旅费-树的直径

题目

蓝桥杯历届习题中有一道题叫大臣的旅费,题目是这样的:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
乍一看这题可以使用DFS,对每个点都用一次DFS,求得最大的旅费(路径权值)即可。但不幸的是,这么做复杂度实在是太高了,压力测试超时了。。。
在这里插入图片描述
于是就上网找各位网友的答案,发现这道题的考点是树的直径,用两遍DFS即可得出答案。

树的直径

在一棵树中,树的直径是两个结点的最大距离(认为树中的边权值非负),要求得这一距离,可以用两遍DFS算法来求解。

第一遍DFS:从根节点开始递归深搜,找到距离根节点最远的结点,记为u。

第二遍DFS:从结点u开始递归深搜,找到距离u最远的结点v。此时,d(u,v)记为树的直径。

证明如下:
直观:树中最远的两个结点一定是叶子节点,不妨设为u和v,其距离d(u,v)。
设有另外两个叶子节点a,b与u,v不完全相同,且满足d(a,b)>d(u,v),下面反证该假设不成立。设结点p是结点u,a,b三者的公共最近祖先,因此有
d ( u , v ) = d ( u , p ) + d ( p , v ) d(u,v)=d(u,p)+d(p,v) d(u,v)=d(u,p)+d(p,v)
而且结点p一定在路径(u,a)或路径(u,b)上(否则p就不是最近的公共祖先了。不妨设结点p在路径(u,a)上,因此
d ( u , a ) = d ( u , p ) + d ( p , a ) ( 1 ) d(u,a)=d(u,p)+d(p,a) \qquad (1) d(u,a)=d(u,p)+d(p,a)(1)
又因为结点p是距离根节点最远的点,因此
d ( u , p ) ≥ d ( b , p ) ( 2 ) d(u,p) \ge d(b,p) \qquad (2) d(u,p)d(b,p)(2)
因为结点v是距离u最远的点,因此
d ( u , v ) ≥ d ( u , b ) ( 3 ) d(u,v) \ge d(u,b) \qquad (3) d(u,v)d(u,b)(3)
结合(1)(2)(3)式得
d ( u , v ) ≥ d ( b , p ) + d ( p , a ) d(u,v) \ge d(b,p) + d(p,a) d(u,v)d(b,p)+d(p,a)

d ( u , v ) ≥ d ( a , b ) d(u,v) \ge d(a,b) d(u,v)d(a,b)
因此,得出结论,u,v距离必是树直径。

解题

由上述结论,求解“大臣的旅费”问题,只需要两遍DFS,第一遍求得距离城市1最远的城市,记为farest,第二遍从farest开始深搜,过程中就可以得到结果result

写好代码后,不幸的是,内存超了,发现是图的表示用了二维数组,没有考虑大规模数据问题。求改后,使用双层嵌套Map用映射来表示非负权值边。顺利AC。这里改为邻接链表表示比较合适,但懒得改了。

还有一点需要注意的是本题中大臣的旅行开销的计算方法很迷惑。。需要注意一下,找到规律后,等差数列求值。

package main;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

public class Main {
  static int N = 0;
  static Map<Integer, Map<Integer, Integer>> edges;
  static int result = 0;
  static int farest = 0;

  public static void main(String[] args) {
    Scanner in = new Scanner(System.in);
    N = in.nextInt();
    edges = new HashMap<Integer, Map<Integer, Integer>>();
    for (int i = 1; i <= N; i++) {
      edges.put(i, new HashMap<>());
    }
    for (int i = 0; i < N - 1; i++) {
      int a = in.nextInt();
      int b = in.nextInt();
      int c = in.nextInt();
      edges.get(a).put(b, c);
      edges.get(b).put(a, c);
    }
    boolean[] vis = new boolean[N + 1];
    vis[1] = true;
    dfs(1, 1, vis, 0, 0);
    result = 0;
    vis = new boolean[N + 1];
    vis[farest] = true;
    dfs(1, farest, vis, 0, 0);
    System.out.println(result);
    in.close();
  }

  public static void dfs(int k, int city, boolean[] vis, int cost, int dis) {
//    System.out.println("city: " + city + " k: " + k + " cost: " + cost);
    for (Integer next : edges.get(city).keySet()) {
      if (!vis[next]) {
        vis[next] = true;
        int pre_cost =
            (21 + edges.get(city).get(next) + 2 * dis) * edges.get(city).get(next) / 2 + cost;
        dfs(k + 1, next, vis, pre_cost, dis + edges.get(city).get(next));
        vis[next] = false;
      }
    }
    if (cost > result) {
      result = cost;
      farest = city;
    }
  }
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
如果你想使用Python编写一个程序来计算大臣旅费,你需要明确旅费的计算规则和参数。例如,你需要知道旅行的目的地、交通方式、住宿费用等信息。 一种简单的示例是,假设大臣旅费计算规则是根据目的地的距离和交通方式来计算。你可以使用第三方库如`googlemaps`来获取两地之间的距离,并根据交通方式确定旅行费用。以下是一个使用Google Maps API计算两地之间距离的示例代码: ```python import googlemaps def calculate_travel_expense(origin, destination, mode): # 设置你的Google Maps API密钥 gmaps = googlemaps.Client(key='YOUR_API_KEY') # 获取两地之间的距离(返回结果以公里为单位) result = gmaps.distance_matrix(origin, destination, mode=mode, units='metric') # 提取距离值 distance = result['rows'][0]['elements'][0]['distance']['value'] / 1000 # 转换为公里 # 根据交通方式确定旅费 if mode == 'driving': expense = distance * 0.5 # 假设每公里花费0.5元 elif mode == 'transit': expense = distance * 0.3 # 假设每公里花费0.3元 else: expense = distance * 0.2 # 假设每公里花费0.2元 return expense # 示例:计算从北京到上海的旅费(以驾车方式) origin = "北京" destination = "上海" mode = "driving" expense = calculate_travel_expense(origin, destination, mode) print("旅费:", expense, "元") ``` 请注意,上述示例仅为演示目的,并没有真实的旅费数据。你可以根据实际情况和需求进行相应的修改和扩展。此外,你需要提供有效的Google Maps API密钥才能正常运行此代码。 希望这个示例能帮助到你开始编写计算大臣旅费的Python程序。如果你有其他问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值