树的直径性质 miaomiao problem
miaomiao problem
这是一篇非常详细的有图有证明题解。
思考 a 在哪
结论:a=1
考虑反正:
假设我们
不妨设加边之前
思考 b 在哪
题目问的是最大距离的最小值,想到~二分~。
也对,这也可以二分,我们就来想想如果写这个 check 函数。
对于目前已经设定好的一个答案
容易想到,在这个 check 函数中我们可以先确定一个
我们称由闹心点和两两闹心点之间简单路径上的点组成的联通子图为“闹心树”。
结论:b 为闹心树的直径的最中间位置的任意一点
先设闹心树直径为
前置结论:\
假设上图横着的那个链是一个直径,闹心点的代价
开始证明这个求
怎样计算答案
对于一个目前枚举到的
两个优化
第一个优化
观察~样例输出~随便一棵树,我们发现随着我们的答案的增加(或者说限制的放宽),我们
因此我们可以考虑使用双指针之类的东西(我是枚举答案然后计算
第二个优化
后面我们需要通过考虑两次 DFS 求直径把复杂度降下去。
我们第一次 DFS 选择以
我们还有一个结论,就是这个点一定是任意时刻的闹心树直径之一的端点之一。
证明:闹心树中一定有且仅有一个距离
因此我们直接维护所有闹心点到这个最闹心点的距离即可,距离这个最闹心的点最远的仍然闹心的点和这个最闹心的点的距离就是闹心树直径长度。(维护这个就随便选一个
这样,每一个闹心点只会变成放心点,因此总复杂度为
优美的屎山两题都是一遍过(不想加注释了,代码请自行理解):
// Problem: Distance Tree (easy version)
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/CF1632E1
// Memory Limit: 500 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#define int long long
#define MAXN 300005
using namespace std;
int T, n, dis[MAXN], ans[MAXN], dep[MAXN];
vector<int> e[MAXN], box[MAXN];
void dfs(int u, int k, int *Dis) {
Dis[u] = k;
for (int v : e[u])
if (!Dis[v])
dfs(v, k + 1, Dis);
}
int getdis(int id, int *Dis) {
for (int i = 1; i <= n; i++)
Dis[i] = 0;
dfs(id, 1, Dis);
int maxn = 0, maxni = id;
for (int i = 1; i <= n; i++) {
Dis[i]--;
if (Dis[i] > maxn) {
maxn = Dis[i];
maxni = i;
}
}
return maxni;
}
signed main() {
cin >> T;
while (T--) {
cin >> n;
for (int i = 1; i <= n; i++) {
ans[i] = n;
e[i].clear();
box[i].clear();
}
for (int i = 1, a, b; i < n; i++) {
cin >> a >> b;
e[a].push_back(b);
e[b].push_back(a);
}
int maxdep = getdis(1, dep);
getdis(maxdep, dis);
ans[n] = dep[maxdep];
for (int i = 1; i <= n; i++)
box[dep[i]].push_back(i);
for (int i = n, D = 0; i >= 0; i--) {
if ((D + 1) / 2 <= i)
ans[i - (D + 1) / 2] = min(ans[i - (D + 1) / 2], i);
for (int j : box[i])
D = max(D, dis[j]);
}
for (int i = n - 1; i >= 1; i--)
ans[i] = min(ans[i], ans[i + 1]);
for (int i = 1; i <= n; i++)
cout << ans[i] << " ";
cout << endl;
}
return 0;
}