题目
蓝桥杯历届习题中有一道题叫大臣的旅费,题目是这样的:
乍一看这题可以使用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;
}
}
}