题目大意:有一棵树,从中选取$2$条链,其中任何一条链的端点不能被另一条链包含,求这两条链,使这两条链的公共的点的部分最长,若相同,使得总长度最长。
题解:树形$DP$,因为端点互不包含,所以公共的部分的端点一定有两个及以上的儿子,然后可以把这样的点先全部求出来。求新树的直径就可以满足第一个要求(洛谷题意有锅,没有写在相同情况下要求总长度最长,导致我最后重构代码)。
那如何使得相同情况下总长度最长呢?可以再维护一个以$i$为根的子树内以$i$为一个端点的最长链和次长链,$DP$时以新树的深度为第一关键字,以最长链和次长链长度总和为第二关键字就可以了。
卡点:1.题意错误
2.按最长链和次长链长度总和比较时比较的是转移的两点,而不是两个端点
C++ Code:
#include <cstdio> #define maxn 200010 inline int max(int a, int b) {return a > b ? a : b;} int head[maxn], cnt; struct Edge { int to, nxt; } e[maxn << 1]; inline void add(int a, int b) { e[++cnt] = (Edge) {b, head[a]}; head[a] = cnt; } bool choose[maxn]; int ind[maxn]; int n, root; struct node { int P, M; inline node() {} inline node(int __P, int __M) {M = __M, P = __P;} inline node operator + (const int &rhs) const {return node(P, M + rhs);} inline friend operator < (const node &lhs, const node &rhs) {return lhs.M < rhs.M;} } V[maxn][2]; void dfs(int u, int fa = 0) { if (ind[u] > 1) choose[u] = true; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (v != fa) { dfs(v, u); if (ind[u] == 2 && !choose[v]) choose[u] = false; } } } int f[maxn], F[maxn]; node dfs1(int u, int fa = 0) { V[u][0] = node(u, 0); V[u][1] = node(0, 0); f[F[u] = u] = choose[u]; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (v != fa) { node tmp = dfs1(v, u) + 1; if (f[v] + choose[u] > f[u]) { f[u] = f[v] + choose[u]; F[u] = F[v]; } else if (f[v] + choose[u] == f[u]) { if (choose[v] && V[F[u]][1].M + V[F[u]][0].M <= V[F[v]][1].M + V[F[v]][0].M) { F[u] = F[v]; } } if (V[u][0] < tmp) { V[u][1] = V[u][0]; V[u][0] = tmp; } else if (V[u][1] < tmp) V[u][1] = tmp; } } return V[u][0]; } int main() { scanf("%d", &n); for (int i = 1, a, b; i < n; i++) { scanf("%d%d", &a, &b); add(a, b); add(b, a); ind[a]++, ind[b]++; if (ind[a] >= 3) root = a; if (ind[b] >= 3) root = b; } dfs(root); int x, a, b, c, d; dfs1(root); x = F[root], a = V[F[root]][0].P, c = V[F[root]][1].P; dfs1(x); b = V[F[x]][0].P, d = V[F[x]][1].P; printf("%d %d\n%d %d\n", a, b, c, d); return 0; }