题目大意:给定一棵 $n$ 个节点的树,每条边的长度为 $1$,定义叶子节点为只与一个节点相邻的节点,请你将全部叶子节点划分到 $m$ 个互不相交的集合,使得在同一集合内的节点两两之间的距离不超过 $k$,问 $m$ 最小是多少?$3 \leq n \leq 10^6, 1 \leq k \leq 10^6$
dfs,从底部往上依次合并叶子节点。
令 $dep[i]$ 表示子树 $i$ 中最深的可合并的叶子节点的深度,设 $i$ 有两个儿子 $a,b$,若 $dep[a]+dep[b]\leq k$,则合并这两个集合,$dep[i]=\max(dep[a],dep[b])$;
否则,若 $dep[a]+dep[b]>k$,不妨设 $dep[a]<dep[b]$,那么后面能合并到 $b$ 的节点,一定不如合并到 $a$ 更优,因为
若存在
必有
此时令 $dep[i]=\min(dep[a],dep[b])$ 即可。
实现时需注意,如果 $u$ 不是叶子节点且 $dep[u]=0$,说明子树 $u$ 的全部叶子节点都不能再继续往上合并了,此时应将 $dep[u]$ 设为 $\inf$。
#include <cstdio>
const int N = 1e6+5;
int head[N], pre[N<<1], nxt[N<<1], dep[N], tot, k, cnt;
int read() {
int x = 0; char c = getchar();
while (c < '0' || c > '9') c = getchar();
while (c >= '0' && c <= '9') {
x = (x << 3) + (x << 1) + (c ^ 48);
c = getchar();
}
return x;
}
int max(int x, int y) {
return x > y ? x : y;
}
int min(int x, int y) {
return x < y ? x : y;
}
void add_edge(int u, int v) {
pre[++tot] = head[u];
head[u] = tot, nxt[tot] = v;
}
void dfs(int cur, int fa) {
for (int i = head[cur]; i; i = pre[i])
if (nxt[i] != fa) {
dfs(nxt[i], cur);
if (dep[cur] + dep[nxt[i]] + 1 <= k) {
if (dep[cur]) --cnt; //合并一个后减掉
dep[cur] = max(dep[cur], dep[nxt[i]] + 1);
} else {
dep[cur] = min(dep[cur], dep[nxt[i]] + 1);
}
}
if (!pre[head[cur]]) ++cnt; //记录有几个叶子节点
else if (dep[cur] == 0) dep[cur] = k; //如果不是叶子节点且dep为0, 则不能继续往上统计
}
int main() {
int n = read(); k = read();
for (int i = 1; i < n; ++i) {
int u = read(), v = read();
add_edge(u, v), add_edge(v, u);
}
for (int i = 1; i <= n; ++i)
if (pre[head[i]]) { //根不能为叶子节点
dfs(i, 0); break;
}
printf("%d\n", cnt);
return 0;
}
时间复杂度 $O(n)$