CF1092E 最小直径森林
1 CF1092E 最小直径森林
2 题目描述
给你一个有 \(𝑛\) 个顶点的森林-一个所有连通块都为树的无向图。一棵树的直径(又称为“最长的最短路径”)是树上最长的一条简单路径的边数。你的任务是向图中添加一些边(也可能不用添加),使它成为一棵树,并且树的直径最小。如果有多个正确答案,输出其中的任何一个。
3 题解
我们发现:如果要使连接后的整棵树的直径最小,我们必须使得连接后所有树的直径以一种尽可能平均的方案连接在一起。我们很容易能想到:可以将所有独立的树的直径上的中点全部连接到直径最大的那棵树的直径的中点上。我们先来证明一下为什么要连接中点:如果我们连接的不是中点,那么直径肯定会被分为一长一短两条路径。我们只要每次选取每棵树上较长的那条路径作为直径的一部分,最终算出的直径肯定不是最小的直径。
我们接着证明为什么要全部连接到直径最长的树的中点上面:如果我们连的不是最长的中点上面,那么我们可以先绕几圈,多连接几棵树,最后再连到直径最长的树上面,导致答案不是最小的直径。如果所有的中点都连接到这直径最长的树的中点上面,那么如果想算上这个树的直径,就只能再加一棵单独树的直径:这是因为树上的路径不能重复。
4 代码(空格警告):
#include <iostream>
#include <cstring>
using namespace std;
const int N = 2005;
int n, m, u, v, tot, maxn, p, cnt, maxn2, mtr;
int head[N], last[N], ver[N], dis[N], now[N], ans;
int cen[N], len[N];
bool f[N];
void add(int x, int y)
{
ver[++tot] = y;
last[tot] = head[x];
head[x] = tot;
}
void dfs(int x, int fa)
{
for (int i = head[x]; i; i = last[i])
{
int y = ver[i];
if (y == fa) continue;
dis[y] = dis[x]+1;
f[y] = 1;
dfs(y, x);
}
}
void dfs2(int x, int fa, int d)
{
bool flag = 0;
now[d] = x;
for (int i = head[x]; i; i = last[i])
{
int y = ver[i];
if (y == fa) continue;
flag = 1;
dfs2(y, x, d+1);
}
if (!flag)
{
if (maxn < d)
{
maxn = d;
ans = now[d/2+1];
}
}
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
cin >> u >> v;
add(u, v);
add(v, u);
}
for (int i = 1; i <= n; i++)
{
memset(dis, 0, sizeof(dis));
if (!f[i])
{
maxn = 0;
f[i] = 1;
dfs(i, -1);
for (int j = 1; j <= n; j++)
{
if (f[j] && maxn <= dis[j])
{
maxn = dis[j];
p = j;
}
}
maxn = 0;
dfs2(p, -1, 1);
cen[++cnt] = ans;
len[cnt] = maxn;
}
}
for (int i = 1; i <= cnt; i++)
{
if (maxn2 < len[i])
{
maxn2 = len[i];
mtr = i;
}
}
for (int i = 1; i <= cnt; i++)
{
if (i == mtr) continue;
add(cen[i], cen[mtr]);
add(cen[mtr], cen[i]);
}
memset(dis, 0, sizeof(dis));
dfs(1, -1);
dis[1] = 0;
maxn = 0;
for (int i = 1; i <= n; i++)
{
if (maxn < dis[i])
{
maxn = dis[i];
p = i;
}
}
maxn = 0;
dfs2(p, -1, 1);
cout << maxn-1 << endl;
for (int i = 1; i <= cnt; i++)
{
if (i != mtr) cout << cen[i] << " " << cen[mtr] << endl;
}
return 0;
}