旅游规划

旅游规划

$W$ 市的交通规划出现了重大问题,市政府下定决心在全市各大交通路口安排疏导员来疏导密集的车流。

但由于人员不足,$W$ 市市长决定只在最需要安排人员的路口安排人员。

具体来说,$W$ 市的交通网络十分简单,由 $n$ 个交叉路口和 $n−1$ 条街道构成,交叉路口路口编号依次为 $0,1,…,n−1$ 。

任意一条街道连接两个交叉路口,且任意两个交叉路口间都存在一条路径互相连接。

经过长期调查,结果显示,如果一个交叉路口位于 $W$ 市交通网最长路径上,那么这个路口必定拥挤不堪。

所谓最长路径,定义为某条路径 $p= \left( {v_{1},v_{2},…,v_{k}} \right)$,路径经过的路口各不相同,且城市中不存在长度大于 $k$ 的路径(因此最长路径可能不唯一)。

因此 $W$ 市市长想知道哪些路口位于城市交通网的最长路径上。

输入格式

第一行包含一个整数 $n$。

之后 $n−1$ 行每行两个整数 $u,v$,表示编号为 $u$ 和 $v$ 的路口间存在着一条街道。

输出格式

输出包括若干行,每行包括一个整数——某个位于最长路径上的路口编号。

为了确保解唯一,请将所有最长路径上的路口编号按编号顺序由小到大依次输出。

数据范围

$1 \leq n \leq 2 \times {10}^{5}$

输入样例:

10
0 1
0 2
0 4
0 6
0 7
1 3
2 5
4 8
6 9

输出样例:

0
1
2
3
4
5
6
8
9

 

解题思路

  题意就是求树的直径上的所有点,其中可能存在多条树的直径。

  这里用树形dp来求树的直径。

  对于一颗树,当我们选定一个点为根后,那么对于任何一条路径,都存在一个唯一的最高点。即不管是哪条路径,其最高点是唯一的。如图:

  直径是所有路径里面的最大值。将所有路径按照最高点进行分类,划分集合,则最高点为$1$的是一类,最高点为$2$的是一类,......,以此类推,一共可以分成$n$组,树的直径就是某一组中的路径的最大值。

  记每个以$i$为最高点,且以$i$为端点的所有路径的最大值为$d_{1}\left[ i \right]$,次大值为$d_{2}\left[ i \right]$。

  对于根节点,先求出所有子节点的最大路径(即以子节点端点的最长的路径长度,同时子节点也是这条路径的最高点)$d_{1}\left[ i \right]$。然后从这些$d_{1}\left[ i \right]$中,找到最大值和次大值,从而得到以根节点为最高点的,且以根节点为端点的所有路径的最大值$d_{1}\left[ root \right]$和次大值$d_{2}\left[ root \right]$。

  接下来我们从根节点出发,选择两个以子节点为端点的路径与根节点连成一条更长的路径,此时根节点仍为路径的最高点,但路径不是以根节点为端点了。要使得路径长度最大,就是$d_{1}\left[ root \right] + d_{2}\left[ root \right]$。同时这条最长路径,对应以根节点为最高点的那个集合的最大值。

  因此要求树的直径,就是求以每一个结点为根节点的$d_{1}\left[ root \right] + d_{2}\left[ root \right]$,树的直径就是这些值中的最大值。

  求最大路径的题可以看这篇博文:大臣的旅费:https://www.cnblogs.com/onlyblues/p/15940402.html

  但这题并不是求树的直径,而是树的直径上的所有点。方法是遍历每一个点,看一下这个点是否在树的直径上。即选择两个与该结点相邻的结点(叶子结点除外),将以这两个结点为端点的路径与该结点相连,与上面的做法相同,判断这条连接的路径是否为树的直径就可以了。

  对于树中任何一个结点,都有一个向上的结点(这个结点的父节点)和向下的若干个结点(子节点。叶子结点除外)。对于向下的子节点,前面我们已经求得最大路径长度$d_{1}\left[ root \right]$和次大路径长度$d_{2}\left[ root \right]$。对于向上的父结点,这里记向上的最大路径长度为$up \left[ root \right]$,表示以根节点为端点(不是最高点)的一条向上的路径,这条路径长度的取值又取决于其父节点的$up \left[ fa \right]$,并且要取尽可能大的值。我们就是要找到$d_{1}\left[ root \right]$、$d_{2}\left[ root \right]$、$up \left[ root \right]$这三个的最大值和次大值,两者相加,如果长度等于树的直径,那么这个点就在树的直径上,否则不在。

  从根节点往上走,走的到父节点,再从父节点走的话又可以走多远。这个最远的值加上$1$就是$up \left[ root \right]$。从根节点的父节点$fa$走有两种值:

  • 从$fa$往上继续走,$up \left[ fa \right]$。
  • 从$fa$往下走:
    • 如果根节点$root$在$d_{1}\left[ fa \right]$这条路径上,那么不能用这条路径。由于要取尽可能大的值,因此这种情况要选择$d_{2}\left[ fa \right]$。
    • 如果根节点$root$不在$d_{1}\left[ fa \right]$这条路径上,那么就取$d_{1}\left[ fa \right]$。

  最后$up \left[ root \right]$就是这两种情况的最大值再加上$1$。本质就是找一条从父节点出发的最长路径,这条路径不能够经过子节点且要是最长的。

  为了判断根节点$root$是否在$d_{1}\left[ fa \right]$这条路径上,这里还需要开一个数组来记录每个结点的最长路径$d_{1}\left[ root \right]$所对应的那个子节点。

  求$d_{1}\left[ root \right]$、$d_{2}\left[ root \right]$是一个自下而上的过程,而求$up \left[ root \right]$则是一个自上而下的过程。

  AC代码如下:

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 using namespace std;
 5 
 6 const int N = 2e5 + 10, M = N << 1;
 7 
 8 int head[N], e[M], ne[M], idx;
 9 int d1[N], d2[N], son[N], up[N];
10 int maxd;
11 
12 void add(int v, int w) {
13     e[idx] = w, ne[idx] = head[v], head[v] = idx++;
14 }
15 
16 void dfs_d(int src, int pre) {
17     for (int i = head[src]; i != -1; i = ne[i]) {
18         if (e[i] != pre) {
19             dfs_d(e[i], src);   // 求子节点的最大路径长度
20             
21             // 更新根节点的最大路径长度和次大路径长度
22             int d = d1[e[i]] + 1;
23             if (d >= d1[src]) {
24                 d2[src] = d1[src];
25                 d1[src] = d;
26                 son[src] = e[i];
27             }
28             else if (d > d2[src]) {
29                 d2[src] = d;
30             }
31         }
32     }
33     
34     maxd = max(maxd, d1[src] + d2[src]);    // 树的直径就是每个结点的d1+d2,取最大值
35 }
36 
37 void dfs_up(int src, int pre) {
38     for (int i = head[src]; i != -1; i = ne[i]) {
39         if (e[i] != pre) {
40             // 自下而上的过程,当枚举都某个结点时,这个结点的up值已经确定,则其子节点的up值也就确定了
41             if (son[src] == e[i]) up[e[i]] = max(up[src], d2[src]) + 1; // 如果子节点在src的d1这条路径上,那么不可以取d1[src]
42             else up[e[i]] = max(up[src], d1[src]) + 1;  // 如果子节点不在src的d1这条路径上,可以取d1[src]
43             dfs_up(e[i], src);
44         }
45     }
46 }
47 
48 int main() {
49     memset(head, -1, sizeof(head));
50     
51     int n;
52     scanf("%d", &n);
53     for (int i = 0; i < n - 1; i++) {
54         int v, w;
55         scanf("%d %d", &v, &w);
56         add(v, w), add(w, v);
57     }
58     
59     dfs_d(0, -1);
60     dfs_up(0, -1);
61     
62     for (int i = 0; i < n; i++) {
63         int d[3] = {d1[i], d2[i], up[i]};
64         sort(d, d + 3);
65         if (d[1] + d[2] == maxd) printf("%d\n", i); // 取每个结点的d1,d2,up中的最大的两个连成一条路径,再判断是不是树的直径长度
66     }
67     
68     return 0;
69 }

 

参考资料

  AcWing 1078. 旅游规划(蓝桥杯C++ AB组辅导课):https://www.acwing.com/video/792/

posted @ 2022-03-13 09:05  onlyblues  阅读(132)  评论(0编辑  收藏  举报
Web Analytics