题目链接:http://codeforces.com/contest/1092/problem/E
题意:现在有 n n n个节点, m m m条边,你需要添加 ( n − 1 ) − m (n-1)-m (n−1)−m条边,然后让 n n n个节点形成一棵树,要求树的直径最小。
解题心得:
- 现在已经有 m m m条边,那么说明已经形成了很多颗树,我们需要做的是将树连接再一起然后直径最小,想一想其实如果能够以当前树中的最大直径为总树的直径那么总树直径肯定是最小的。首先需要做的是找出每颗树的直径,然后在直径上找出树的中心(树的中心:树上的一点到其他点的最长距离最小),将所有树的中心记录下来和最长直径树的中心记录下来记为 V V V,最后答案就是将所有树的中心和 V V V连一条边。这样肯定是最小的。但是有需要注意的一点就是这样连接之后总树的直径并不一定是之前最长树的直径,还需要判断一下中心连接之后是否增长了,增长的情况的情况主要就是直径长度为奇数的情况下中心有两个,还有就是长度相同的两/三个最长直径连接之后长度会分别+1/+2。
- 关于找出树的直径可以这样找的,先随便找一点深搜到最远的一个点记为 p o i n t 1 point1 point1,然后从 p o i n t 1 point1 point1再次深搜找到第二个最远的点 p o i n t 2 point2 point2,这时 p o i n t 1 point1 point1到 p o i n t 2 point2 point2的路径就是就是树的直径。
#include <bits/stdc++.h>
using namespace std;
typedef complex<double> cp;
typedef long long ll;
const ll maxn = 2e5+100;
const double pi = acos(-1);
vector <int> ve[maxn], ans, len;//记录边关系,多棵树的中心点,多棵树的直径
int n, m, d_len, P=1;//记录节点个数,边条数,多棵树中最长直径长度,最长直径树的中心
bool vis[maxn];
void init() {
scanf("%d%d", &n, &m);
for(int i=1;i<=m;i++) {
int a, b; scanf("%d%d",&a, &b);
ve[a].push_back(b);
ve[b].push_back(a);
}
}
int Max, point;
void dfs(int pre, int now , int deep) {
vis[now] = true;
if(deep > Max) {
Max = deep;
point = now;
}
for(int i=0;i<ve[now].size();i++) {
int v = ve[now][i];
if(v == pre) continue;
dfs(now, v, deep+1);
}
}
int certen_point;//树的中心点
bool certen(int pre, int x, int y, int deep, int len) {
bool flag = false;
if(x == y) {
return true;
}
for(int i=0;i<ve[x].size();i++) {
int v = ve[x][i];
if(v == pre) continue;
flag |= certen(x, v, y, deep+1, len);
if(flag) {
if(max(len-deep, deep) < Max) {
Max = max(len-deep, deep);
certen_point = x;
}
return flag;
}
}
return flag;
}
void find_certen(int x) {
if(ve[x].size() == 0) {//入宫是一个度为0的点
certen_point = x;
return ;
}
//找出直径
Max = point = 0;
dfs(-1, x, 0);
int point1 = point;
Max = point = 0;
dfs(-1, point1, 0);
int point2 = point;
int temp = Max;//记录直径的长度
Max = 1e9;
certen(-1, point1, point2, 0, temp);//在直径中找到中心
if(temp > d_len) {//记录最长直径上的中心
d_len = temp;
P = certen_point;
}
len.push_back(temp);
}
int main() {
// freopen("1.in.txt", "r", stdin);
init();
if(n == 1) {//如果只一个点直接输出
printf("0");
return 0;
}
for(int i=1;i<=n;i++) {
if(!vis[i]) {//找出每一棵树的中心和直径长度
find_certen(i);//查找中心
ans.push_back(certen_point);
}
}
len.push_back(0);
len.push_back(0);
sort(len.begin(), len.end());
reverse(len.begin(), len.end());
d_len = len[0];
//当中心点之间连接边后总树的直径可能产生的变化
if(d_len < (len[0]+1)/2 + (len[1]+1)/2 + 1) {
d_len = (len[0]+1)/2 + (len[1]+1)/2 + 1;
}
if(d_len > len[0] && len[0] == len[1] && len[1] == len[2]) d_len++;
if(ans.size() == n && n == 2) d_len = 1;//m==0
if(ans.size() == n && n > 2) d_len = 2;//没有边m==0
printf("%d\n", d_len);
for(int i=0;i<ans.size();i++) {
if(ans[i] == P) continue;
printf("%d %d\n", ans[i], P);
}
return 0;
}