题解 P3174 [HAOI2009]毛毛虫
题意简述
给定一棵 \(n\) 个点树,求一条路径,使得这个路径上的点以及直接与这条路径上的点相连的点的个数和最大。输出这个个数。
\(1 \leq n \leq 3\times 10^5\)。
Solution
定义一条路径的权值为这个路径上的点以及直接与这条路径上的点相连的点的个数和。
定义 \(d(i)\) 表示与节点 \(i\) 直接相连的点数。
我们可以通过 \(dp(i)\) 求出以 \(i\) 为开头(同时 \(i\) 要是深度最小的那个点)的权值的最大值。具体实现是 DFS。
在 DFS 到 \(i\) 的时候,可以顺便求一下以 \(i\) 为最高点的路径的权值最大值(其实就是 \(i\) 的子树中的某个点 \(\to i\to\) \(i\) 的子树中的另外一个点,类似于求树的直径的 \(dp\) 做法)。
类似于求树的直径的 \(dp\) 做法,我们记录一下以 \(i\) 的子节点为最高点(同时 \(i\) 的子节点要是深度最小的那个点)的权值的最大值和次大值,如果分别记为 \(maxn\) 和 \(cmax\) 的话,那么每次可以用 \(maxn + cmax - 1 + d(i)-2\) 来更新答案。画个图就可以理解,\(maxn + cmax - 1\) 求的是这两个子节点合并起来的节点个数(\(i\) 这个节点被重复算了 \(2\) 次),\(d(i)-2\) 是 \(i\) 这个节点除了这两个子节点以外的直接连接的节点个数。
最后以 \(i\) 为最高点的权值最大值有两种情况:
- \(i\) 是叶子结点:为 \(d(i)+1\) 。
- \(i\) 不是叶子结点:为 \(maxn+d(i)-1\) (减去重复算的那个子节点,根节点在子节点那里已经算过了不需要再算)
注意:最后要使用 \(dp(1)\) 的返回值更新答案(可能树是一条链导致不存在 \(cmax\))。
代码如下:
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <iostream>
#include <queue>
using namespace std;
inline int read() {
int num = 0 ,f = 1; char c = getchar();
while (!isdigit(c)) f = c == '-' ? -1 : f ,c = getchar();
while (isdigit(c)) num = (num << 1) + (num << 3) + (c ^ 48) ,c = getchar();
return num * f;
}
const int N = 3e5 + 5 ,M = 6e5 + 5 ,INF = 0x3f3f3f3f;
struct Edge {
int to ,next;
Edge (int to = 0 ,int next = 0) : to(to) ,next(next) {}
}G[M]; int head[N] ,idx;
inline void add(int u ,int v) {
G[++idx] = Edge(v ,head[u]); head[u] = idx;
G[++idx] = Edge(u ,head[v]); head[v] = idx;
}
int ans = 0;
inline int dfs(int now ,int fa) {
int d = (fa != 0) ,maxn = -INF ,cmax = -INF;
for (int i = head[now]; i ; i = G[i].next) {
int v = G[i].to;
if (v == fa) continue;
d++;
int t = dfs(v ,now);
if (t >= maxn) cmax = maxn ,maxn = t;
else if (t > cmax) cmax = t;
}
ans = max(ans ,maxn + cmax - 1 + d - 2);
if (maxn == -INF) return d + 1;
//注意特盘叶子结点
return maxn + d - 1;
}
int n;
signed main() {
n = read(); {int useless = read();}
for (int i = 1; i <= n - 1; i++) {
int u = read() ,v = read();
add(u ,v);
}
int t = dfs(1 ,0);
printf("%d\n" ,max(ans ,t));
return 0;
}