藍橋杯-大臣的旅費-樹的直徑

題目

藍橋杯歷屆習題中有一道題叫大臣的旅費,題目是這樣的:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
乍一看這題可以使用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)
而且結點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)
又因爲結點p是距離根節點最遠的點,因此
d(u,p)d(b,p)(2)d(u,p) \ge d(b,p) \qquad (2)
因爲結點v是距離u最遠的點,因此
d(u,v)d(u,b)(3)d(u,v) \ge d(u,b) \qquad (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(a,b)d(u,v) \ge 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;
    }
  }
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章