大臣的旅费

大臣的旅费

很久以前,$T$ 王国空前繁荣。

为了更好地管理国家,王国修建了大量的快速路,用于连接首都和王国内的各大城市。

为节省经费,$T$ 国的大臣们经过思考,制定了一套优秀的修建方案,使得任何一个大城市都能从首都直接或者通过其他大城市间接到达。

同时,如果不重复经过大城市,从首都到达每个大城市的方案都是唯一的。

J是T国重要大臣,他巡查于各大城市之间,体察民情。

所以,从一个城市马不停蹄地到另一个城市成了 $J$ 最常做的事情。

他有一个钱袋,用于存放往来城市间的路费。

聪明的J发现,如果不在某个城市停下来修整,在连续行进过程中,他所花的路费与他已走过的距离有关,在走第 $x$ 千米到第 $x+1$ 千米这一千米中($x$是整数),他花费的路费是 $x+10$ 这么多。也就是说走 $1$ 千米花费 $11$,走 $2$ 千米要花费 $23$。

$J$ 大臣想知道:他从某一个城市出发,中间不休息,到达另一个城市,所有可能花费的路费中最多是多少呢?

输入格式

输入的第一行包含一个整数 $n$,表示包括首都在内的T王国的城市数。

城市从 $1$ 开始依次编号,$1$ 号城市为首都。

接下来 $n−1$ 行,描述 $T$ 国的高速路($T$国的高速路一定是 $n−1$ 条)。

每行三个整数 $P_{i},Q_{i},D_{i}$,表示城市 $P_{i}$ 和城市 $Q_{i}$ 之间有一条双向高速路,长度为 $D_{i}$ 千米。

输出格式

输出一个整数,表示大臣 $J$ 最多花费的路费是多少。

数据范围

$1 \leq n \leq 10^{5}$,
$1 \leq P_{i},Q_{i} \leq n$,
$1 \leq D_{i} \leq 1000$

输入样例:

5 
1  2  2 
1  3  1 
2  4  5 
2  5  4 

输出样例:

135

 

解题思路

两遍dfs解法

  根据题意,这是一个无环连通图,也就是一棵树。

  题目是问我们树上的最长路径是多少,也就是问我们树的直径。

  树的直径:一棵树上长度最长的路径。

  求树的直径的其中一种方法是用dfs:

  1. 任取一点$x$,求剩余的点到$x$的距离,并在这些点中找到一个到$x$距离最大的点$y$。
  2. 求所有点到$y$的距离,到$y$最大的距离就是树的直径。

  我们从第一步找到一个距离$x$最远的点$y$一定是直径上的一个端点,因此根据第二步找到的距离$y$最远的点所构成的路径就是一条树的直径。下面证明这种做法是正确的。

  用反证法,假设$y$不是任何一条直径的端点。

  情况$1$,假设存在一条直径,与$x$到$y$的路径有交点:

  假设$v$,$w$是直径的两个端点,与$x$到$y$的路径上有交点$u$。

  因为$y$是距离$x$最远的一个点之一,因此有$d_{xuy} \geq d_{xuv}$,有公共的一段$d_{xu}$,因此有$d_{uy} \geq d_{uv}$。加上同一段$d_{uw}$,因此有$d_{yuw} \geq d_{vuw}$。又因为$d_{vw}$是一条直径,因此$d_{yw}$也是一条直径,即$y$是直径的端点,与假设矛盾。

  情况$2$,假设存在一条直径,与$x$到$y$的路径没有交点:

  假设$v$,$w$是直径的两个端点,由于是一颗树(连通),因此在$x$到$y$的路径中必然会有一个节点$a$($a$也可能是$x$或$y$),与$v$到$w$的路径中的一个节点$b$(同理$b$也可能是$v$或$w$)是可以相互到达的(即连通)。

  因为$y$是距离$x$最远的一个点之一,因此有$d_{xay} \geq d_{xabv}$,有公共的一段$d_{xa}$,因此有$d_{ay} \geq d_{abv}$。由于每一条路径的长度大于$0$,$d_{ab} > 0$,因此有$d_{ay} > d_{bv}$,$d_{yab} > d_{bv}$。加上同一段$d_{bw}$,因此有$d_{yabw} > d_{vw}$。又因为$d_{vw}$是一条直径,而$d_{yabw} > d_{vw}$,矛盾。

  因此正确性得到证明,$y$必然是某条直径的端点。

  AC代码如下:

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 using namespace std;
 5 
 6 const int N = 1e5 + 10, M = N << 1;
 7 
 8 int head[N], e[M], wts[M], ne[M], idx;
 9 int dist[N];
10 
11 void add(int v, int w, int wt) {
12     e[idx] = w, wts[idx] = wt, ne[idx] = head[v], head[v] = idx++;
13 }
14 
15 void dfs(int src, int pre, int d) {
16     dist[src] = d;
17     for (int i = head[src]; i != -1; i = ne[i]) {
18         if (e[i] != pre) {
19             dfs(e[i], src, d + wts[i]);
20         }
21     }
22 }
23 
24 int main() {
25     memset(head, -1, sizeof(head));
26     
27     int n;
28     scanf("%d", &n);
29     for (int i = 0; i < n - 1; i++) {
30         int v, w, wt;
31         scanf("%d %d %d", &v, &w, &wt);
32         add(v, w, wt), add(w, v, wt);
33     }
34     
35     dfs(1, 0, 0);   // 任取一点1,求其余点到1的距离
36     
37     // 找到距离1最远的点maxv
38     int maxv = 1;
39     for (int i = 1; i <= n; i++) {
40         if (dist[i] > dist[maxv]) maxv = i;
41     }
42     
43     dfs(maxv, 0, 0);    // 求所有点到maxv的距离
44     
45     // 找到离maxv最远的距离,这个距离就是树的直径
46     int maxd = -1;
47     for (int i = 1; i <= n; i++) {
48         maxd = max(maxd, dist[i]);
49     }
50     printf("%lld", 10 * maxd + maxd * (maxd + 1ll) / 2);
51     
52     return 0;
53 }

 

树形dp解法

  树形dp解法的原理看考这篇博文:旅游规划:https://www.cnblogs.com/onlyblues/p/15998496.html

  AC代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 1e5 + 10, M = N << 1;

int head[N], e[M], wts[M], ne[M], idx;
int d1[N], d2[N];   // d1[i]表示以i为最高点的最长路径,d2[i]表示以i为最高点的次长路径
int maxd;   // 树的直径

void add(int v, int w, int wt) {
    e[idx] = w, wts[idx] = wt, ne[idx] = head[v], head[v] = idx++;
}

void dfs(int src, int pre) {
    for (int i = head[src]; i != -1; i = ne[i]) {
        if (e[i] != pre) {
            dfs(e[i], src); // 求子节点e[i]的最长路径
            
            // 更新最大值和次大值
            int d = d1[e[i]] + wts[i];
            if (d > d1[src]) {
                d2[src] = d1[src];
                d1[src] = d;
            }
            else if (d > d2[src]) {
                d2[src] = d;
            }
        }
    }
    
    maxd = max(maxd, d1[src] + d2[src]);
}

int main() {
    memset(head, -1, sizeof(head));
    
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n - 1; i++) {
        int v, w, wt;
        scanf("%d %d %d", &v, &w, &wt);
        add(v, w, wt), add(w, v, wt);
    }
    
    dfs(1, -1);
    printf("%lld", 10 * maxd + maxd * (maxd + 1ll) / 2);
    
    return 0;
}

 

参考资料

  AcWing 1207. 大臣的旅费(蓝桥杯C++ AB组辅导课):https://www.acwing.com/video/710/

posted @ 2022-02-26 21:28  onlyblues  阅读(79)  评论(0编辑  收藏  举报
Web Analytics