『树直径·树形DP』「四校联考」平衡树

    xiaoxiao2022-07-14  151

    题解

    题意很简明扼要,在一棵树上删除一条边并添加一条边,使得树的最长链最小。

    很显然,删去的边一定在树的最长链上;否则,尽管其他的边如何变小,树的最长链是始终不变的。

    因此我们需要枚举最长链上的每一条边,并将其删除;最后统计删去后的最长链是多少。

    显然,当一棵树 t r e e tree tree的最长链删去以后,便会分裂成子树 t r e e 1 tree1 tree1 t r e e 2 tree2 tree2;那么最后的直径的长度只有可能是这三种情况的最大值:

    1. t r e e 1 tree1 tree1的直径。假设 t r e e 1 tree1 tree1中,原来最长链的一段为 r o o t 1 root1 root1.2. t r e e 2 tree2 tree2的直径。假设 t r e e 2 tree2 tree2中,原来最长链的一段为 r o o t 2 root2 root2.3. t r e e 1 tree1 tree1的某一个点和 t r e e 2 tree2 tree2的某一个点连接的最长链。

    其中 r o o t 1 root1 root1 r o o t 2 root2 root2可以很容易用两边 d f s dfs dfs或者 b f s bfs bfs找到。

    我们考虑一下如何计算具体的答案:

    先以tree1直径的计算方法为例:

    我们知道 t r e e 1 tree1 tree1的直径一定不经过 r o o t 2 root2 root2。我们可以以 r o o t 2 root2 root2为根跑一边树形DP,令 f [ x ] f[x] f[x]表示以 i i i为根的子树中,最能达到的最大深度。显然有: f [ x ] = m a x ( f [ y ] + 1 ) , y ∈ s o n ( x ) f[x]=max(f[y]+1),y∈son(x) f[x]=max(f[y]+1),yson(x) M a x 1 [ x ] Max1[x] Max1[x]表示以 x x x为根的子树中的最长链,也不难得到: M a x 1 [ x ] = m a x ( M a x 1 [ y ] , f [ y 1 ] + f [ y 2 ] + 2 ) , y , y 1 , y 2 ∈ s o n ( x ) Max1[x]=max(Max1[y],f[y_1]+f[y_2]+2),y,y_1,y_2∈son(x) Max1[x]=max(Max1[y],f[y1]+f[y2]+2),y,y1,y2son(x) 假设 t r e e 1 tree1 tree1中的 u u u t r e e 2 tree2 tree2中的v连接,那么情况 1 1 1的答案就是 M a x 1 [ u ] Max1[u] Max1[u],我们很容易根据自己设的定义得知。同理,情况 2 2 2的答案为 M a x 2 [ v ] Max2[v] Max2[v].

    现在我们需要考虑情况 3 3 3的答案:显然在连线时,在 t r e e 1 tree1 tree1的一部分一定在 t r e e 1 tree1 tree1的直径上;在tree2的一部分一定在 t r e e 2 tree2 tree2的直径上。显然,取 t r e e 1 tree1 tree1直径的一半, t r e e 2 tree2 tree2直径的一半并连线一定是最优的。如果直径是奇数,对半分后直径一定在较长的一遍,所以除以 2 2 2以后需要向上取证。我们具体可以表达为: ⌈ M a x 1 [ u ] 2 ⌉ + ⌈ M a x 2 [ v ] 2 ⌉ + 1 \lceil \frac{Max1[u]}{2}\rceil + \lceil \frac{Max2[v]}{2}\rceil+1 2Max1[u]+2Max2[v]+1.

    暴力枚举最长链上的每一条边,对上述三种情况的最大值求最小值即可。

    这道题综合了直径的两种求法,同时也运用了树形DP和枚举的思想,可能看了题解后思路非常清晰但是不可否认还是很难哒。

    时间复杂度:最坏 O ( N ) O(N) O(N).在数据为一条链是为 O ( N ) O(N) O(N).

    代码也就100行…

    代码如下:

    #include <bits/stdc++.h> using namespace std; inline int read(void) { int s = 0, w = 1;char c = getchar(); while (c < '0' || c > '9') { if (c == '-') w = -1; c = getchar();} while (c >= '0' && c <= '9') s = s*10+c-48, c = getchar(); return w * s; } const int N = 600000; int root1, root2, ans, n, Max; int f[N]; int Max1[N]; int Max2[N]; int deep[N]; vector <int> a[N]; void dfs1(int x,int fa) { deep[x] = deep[fa] + 1; if (deep[x] > Max) Max = deep[x], ans = x; for (int i=0;i<a[x].size();++i) { int y = a[x][i]; if (y == fa) continue; dfs1(y,x); } return; } //求距离起点最远的点 void find_root(void) { Max = 0, ans = 0, deep[1] = 0; dfs1(1,0), root1 = ans; Max = 0, ans = 0, deep[root1] = 0; dfs1(root1, 0), root2 = ans; return; } //得到原始直径的两个端点 void dfs2(int x,int fa) { f[x] = 0; for (int i=0;i<a[x].size();++i) { int y = a[x][i]; if (y == fa) continue; dfs2(y,x); Max1[x] = max(Max1[x], max(f[x]+f[y]+1, Max1[y])); f[x] = max(f[x], f[y]+1); } return; } //以root1为根求每一个点不经过root1的最长链 void dfs3(int x,int fa) { f[x] = 0; for (int i=0;i<a[x].size();++i) { int y = a[x][i]; if (y == fa) continue; dfs3(y,x); Max2[x] = max(Max2[x], max(f[x]+f[y]+1, Max2[y])); f[x] = max(f[x], f[y]+1); } return; } //以第二个点为根不经过root2的最长链 bool dfs4(int x,int fa) { if (x == root2) return 1; for (int i=0;i<a[x].size();++i) { int y = a[x][i]; if (y == fa) continue; if (dfs4(y,x) == 0) continue; int ans1 = max(Max1[y],Max2[x]); int ans2 = (Max1[y]+1>>1)+(Max2[x]+1>>1)+1; ans = min(max(ans1,ans2),ans); return 1; } return 0; } //对直径进行删边操作 int main(void) { freopen("balance.in","r",stdin); freopen("balance.out","w",stdout); n = read(); ans = INT_MAX; for (int i=1;i<n;++i) { int x = read(); int y = read(); a[x].push_back(y); a[y].push_back(x); } find_root(); dfs2(root1, 0); dfs3(root2, 0); ans = INT_MAX; dfs4(root1, 0); printf("%d\n", ans); return 0; }
    最新回复(0)