题意:
在一棵树中,可以从根节点往其他节点加一条边,使得根节点到其他所有节点的距离和最小,输出最小的距离和。
思路:
我们考虑在加的一条边为$1 o v$,那么在树上从$1 o v$的路径上,如果有一个点$y$到$v$比到$1$更近,那么这个点$y$的子树里的所有
点都到$v$更近。那么我们找到离根最近的点$y$,那么$y$子树中的所有点都是到$v$更近。
我们考虑:
$f[u]$表示如果添加了$1 o u$这条边的最小距离和是多少。
$g[u]$表示如果添加了$1 o u$这条边有多少点到$u$的距离比到根的距离更小。
$sze[u]$表示$u$的子树的大小。
那么对于它的一个儿子$v$,$f[v] = f[u] - 2 cdot sze[v] + g[u]$。
因为原来到$u$更优的,那么到$v$至少不会比到根更差,但是$v$的子树中的贡献要重新算。
然后更新一下儿子节点的$g[u]$就好了,这个时候到$v$和到根一样优的点就被删去了。
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 #define ll long long 5 #define N 200010 6 #define INFLL 0x3f3f3f3f3f3f3f3f 7 #define DEG 20 8 int n; 9 vector <int> G[N]; 10 11 int dep[N], fa[DEG][N], sze[N], k[N]; 12 void DFS1(int u) 13 { 14 k[u] = (dep[u]) / 2 - 1; 15 for (int i = 1; i < DEG; ++i) 16 fa[i][u] = fa[i - 1][fa[i - 1][u]]; 17 sze[u] = 1; 18 for (auto v : G[u]) if (v != fa[0][u]) 19 { 20 fa[0][v] = u; 21 dep[v] = dep[u] + 1; 22 DFS1(v); sze[u] += sze[v]; 23 } 24 } 25 26 int findkth(int u, int k) 27 { 28 for (int i = DEG - 1; i >= 0; --i) 29 if ((k >> i) & 1) 30 u = fa[i][u]; 31 return u; 32 } 33 34 int f[N]; ll g[N], res; 35 void DFS2(int u) 36 { 37 if (u != 1) 38 { 39 if (dep[u] <= 3) 40 { 41 f[u] = sze[u]; 42 g[u] = g[1] - 1ll * (dep[u] - 1) * sze[u]; 43 } 44 else 45 { 46 int pre = fa[0][u]; 47 g[u] = g[pre] - 2 * sze[u] + f[pre]; 48 f[u] = sze[findkth(u, k[u])]; 49 } 50 } 51 res = min(res, g[u]); 52 for (auto v : G[u]) if (v != fa[0][u]) 53 DFS2(v); 54 } 55 56 int main() 57 { 58 int T; cin >> T; 59 while (T--) 60 { 61 scanf("%d", &n); 62 for (int i = 1; i <= n; ++i) G[i].clear(); 63 memset(sze, 0, sizeof sze); 64 for (int i = 1, u, v; i < n; ++i) 65 { 66 scanf("%d%d", &u, &v); 67 G[u].push_back(v); 68 G[v].push_back(u); 69 } 70 dep[1] = 0; DFS1(1); 71 res = INFLL; 72 g[1] = 0; 73 for (int i = 1; i <= n; ++i) 74 g[1] += dep[i]; 75 DFS2(1); 76 printf("%lld ", res); 77 } 78 return 0; 79 }