Solution 1
首先,如果我们已经把这棵可爱的森林连成了一棵更加可爱的树,那么新树的直径是多少呢?
类比CF804D(有我题解),可以得到直径来自下面两种情况之一:
①老树中的直径(即在初始的森林形态时的每棵树);
②对于两个来自不同老树的节点 u , v u,v u,v后,它们之间的长度为:
u u u所在的老树中,以 u u u为一端的路径的最长链+
在新树中, u u u所在的树与 v v v所在的树的距离+
v v v所在的老树中,以 v v v为一端的路径的最长链。
首先,我们考虑该如何连接节点,不考虑在哪些老树之间连边。显然,假设我们连接了节点 u , v u,v u,v,那么 u , v u,v u,v一定是所在老树中直径(最长链)的中点。
此定理很显然,读者自证不难
在初始的森林形态中,定义有 k k k棵树, d i d_i di表示在第 i i i棵树中以直径的中点为一端的路径的最长距离, d i s ( i , j ) dis(i,j) dis(i,j)表示第 i i i棵树到第 j j j棵树的距离, d i a i dia_i diai表示第 i i i棵树的直径。我们需要用一种方式来连接这些树,使得
f = m a x 1 ≤ i ≤ k , 1 ≤ j ≤ k , i ≠ j ( m a x ( d i + d j + d i s ( i , j ) , d i a i , d i a j ) ) f=max_{1≤i≤k,1≤j≤k,i≠j}(max(d_i+d_j+dis(i,j),dia_i,dia_j)) f=max1≤i≤k,1≤j≤k,i=j(max(di+dj+dis(i,j),diai,diaj))
的值最小。
现在,相当于,我们把原来的树套树缩成了一棵普通的树,每个节点都有一个点权 d d d;现在要把这些节点连成一棵树,使得 f f f值最小。
这时,我们需要的就是构造的能力。接着可以发现,我们构造出来的树必须是一个菊花图。
为什么呢?这里帮助各位巨佬感性理解一下:菊花图有一个重要的性质,其直径为 2 2 2。所以, d i s ( i , j ) dis(i,j) dis(i,j)的最大值也只有 2 2 2。
详细证明咕咕咕
于是,我们枚举菊花图的那个入度为 k − 1 k-1 k−1的节点,假设 t t t是目前枚举到的这个节点的编号。每次求出 f f f的值。而 f f f的值似乎只能在 O ( k 2 ) O(k^2) O(k2)的复杂度内求出……
考虑优化一下求 f f f值的方法。根据 m a x max max函数的结合律,我们可以得到:
f = m a x 1 ≤ i ≤ k , 1 ≤ j ≤ k , i ≠ j ( m a x ( d i + d j + d i s ( i , j ) , d i a i , d i a j ) ) f=max_{1≤i≤k,1≤j≤k,i≠j}(max(d_i+d_j+dis(i,j),dia_i,dia_j)) f=max1≤i≤k,1≤j≤k,i=j(max(di+dj+dis(i,j),diai,diaj))
f = m a x ( m a x 1 ≤ i ≤ k , 1 ≤ j ≤ k , i ≠ j ( d i + d j + d i s ( i , j ) ) , m a x 1 ≤ i ≤ k ( d i a i ) ) f=max(max_{1≤i≤k,1≤j≤k,i≠j}(d_i+d_j+dis(i,j)),max_{1≤i≤k}(dia_i)) f=max(max1≤i≤k,1≤j≤k,i=j(di+dj+dis(i,j)),max1≤i≤k(diai))
①当 d i s ( i , j ) = 1 dis(i,j)=1 dis(i,j)=1时,显然 i = t i=t i=t或 j = t j=t j=t。直接枚举找最大值即可,时间复杂度为 O ( k ) O(k) O(k)。
②当 d i s ( i , j ) = 2 dis(i,j)=2 dis(i,j)=2时,显然 i ≠ t i≠t i=t切 j ≠ t j≠t j=t。此时的最大值为 d d d数组中除去编号为 t t t的节点后,剩下的最大值与次大值之和加 2 2 2。于是,我们每次只需要找最大值与次大值即可,时间复杂度为 O ( k ) O(k) O(k)。
在最坏情况下, k = n k=n k=n,此时时间复杂度为 O ( n 2 ) O(n^2) O(n2)。
Solution 2
本文章开头说过,本题存在 O ( n l o g n ) O(nlogn) O(nlogn)或 O ( n ) O(n) O(n)的解法。
现在,我们回过头来看,Soluion 1中有什么可以优化的呢?
①当 d i s ( i , j ) = 1 dis(i,j)=1 dis(i,j)=1时,我们不需要枚举最大值。最大值即为 t t t号节点的 d d d值,与剩下节点 d d d值的最大值之和加 1 1 1。
②只需要每次 O ( 1 ) O(1) O(1)找到除去 t t t节点的最大值与次大值即可优化。
于是,现在我们需要攻克的最后一个关卡就是一个Answer Queries的问题: 每次查询去掉一个节点后,剩下节点的最大值与次大值;每次询问独立。
这个问题有两种做法:
①求出 d d d数组后立即 O ( k l o g k ) O(klogk) O(klogk)地排序;
②求出 d d d数组后,在 O ( k ) O(k) O(k)的复杂度内求出其最大值,次大值与次次大值(即第 3 3 3大的值),每次就可以 O ( 1 ) O(1) O(1)查询。
最后,我们得到了本题的正解,而它是 O ( n ) O(n) O(n)的。
但是由于本蒟蒻过于懒惰,采用了 O ( n l o g n ) O(nlogn) O(nlogn)的做法QAQ
Summary
步骤:
①通过并查集找到森林中的每棵树;
②求出每棵树的直径,与 d d d值(直径可以通过两次 d f s dfs dfs求出);
③枚举菊花图的度数为 k − 1 k-1 k−1的节点,并求出 f f f来更新答案。
本题思维含量较高,是 D i v . 3 Div.3 Div.3的 E E E中偏难的好题,但并不难得到正解。套路性强,码量较大,数据太水。
Code
//O(nlogn)解法
#include <bits/stdc++.h>
#define int long long
#define inf 200000000000007
using namespace std;
int n,m,u,v,cnt=0,len=0,maxv=0,ans=inf,poss;
int head[100005],size[100005],depth[100005],father[100005];
int pos[100005],up[100005],bestson[100005],top[100005];
struct FFT
{
int rt,dia,d,minrt;
}ducati[200005];
struct edge
{
int next;
int to;
}e[200005];
struct node
{
int u,v;
}a[100005];
inline void add_edge(int u,int v)
{
cnt++;
e[cnt].to=v;
e[cnt].next=head[u];
head[u]=cnt;
}
inline int find(int x)
{
if (x!=father[x]) father[x]=find(father[x]);
return father[x];
}
inline void dfs(int now,int fath,int nowdis)
{
up[now]=fath;
size[now]=1;
depth[now]=depth[fath]+1;
int Maxv=0;
if (nowdis>=maxv) maxv=nowdis,u=now;
for (int i=head[now];i;i=e[i].next)
{
if (e[i].to!=fath)
{
dfs(e[i].to,now,nowdis+1);
size[now]+=size[e[i].to];
if (size[e[i].to]>Maxv) Maxv=size[e[i].to],bestson[now]=e[i].to;
}
}
}
inline void dfs1(int now,int fath,int nowtop)
{
top[now]=nowtop;
if (bestson[now]) dfs1(bestson[now],now,nowtop);
for (int i=head[now];i;i=e[i].next)
{
if (e[i].to!=fath&&e[i].to!=bestson[now]) dfs1(e[i].to,now,e[i].to);
}
}
inline void dfs2(int now,int fath,int nowdis)
{
if (now==1) return;
if (nowdis>=maxv) maxv=nowdis,v=now;
for (int i=head[now];i;i=e[i].next)
{
if (e[i].to!=fath) dfs2(e[i].to,now,nowdis+1);
}
}
inline int LCA(int x,int y)
{
while (top[x]!=top[y])
{
if (depth[top[x]]<depth[top[y]]) swap(x,y);
x=up[top[x]];
}
if (depth[x]<depth[y]) return x;
else return y;
}
inline int dis(int x,int y)
{
int tmp=LCA(x,y);
return depth[x]-depth[tmp]+depth[y]-depth[tmp];
}
inline void dfs3(int now,int fath)
{
int res=max(dis(now,u),dis(now,v));
if (ducati[len].d>=res)
{
ducati[len].d=res;
ducati[len].minrt=now-1;
}
for (int i=head[now];i;i=e[i].next)
{
if (e[i].to!=fath) dfs3(e[i].to,now);
}
}
bool cmp(FFT x,FFT y)
{
return x.d>y.d;
}
signed main()
{
cin>>n>>m;
for (int i=1;i<=n+1;i++) father[i]=i;
for (int i=1;i<=m;i++)
{
cin>>u>>v;
u++,v++;
if (u>v) swap(u,v);
father[find(u)]=find(v);
a[i].u=u,a[i].v=v;
add_edge(u,v);
add_edge(v,u);
}
for (int i=1;i<=n+1;i++) father[i]=find(father[i]);
for (int i=2;i<=n+1;i++)
{
if (i==father[i]) add_edge(1,i),add_edge(i,1);
}
for (int i=head[1];i;i=e[i].next)
{
len++;
ducati[len].d=inf;
pos[e[i].to]=len;
maxv=0;
dfs(e[i].to,1,0);
dfs1(e[i].to,1,e[i].to);
maxv=0;
dfs2(u,1,0);
dfs3(e[i].to,1);
ducati[len].dia=dis(u,v);
ducati[len].rt=e[i].to;
}
if (len==1) return cout<<ducati[len].dia<<endl,0;
else if (len==2)
{
int ans=max(ducati[1].d+ducati[2].d+1,max(ducati[1].dia,ducati[2].dia));
cout<<ans<<endl;
cout<<ducati[1].minrt<<' '<<ducati[2].minrt<<endl;
return 0;
}
sort(ducati+1,ducati+len+1,cmp);
maxv=0;
for (int root=1;root<=len;root++) maxv=max(maxv,ducati[root].dia);
for (int root=1;root<=len;root++)
{
int point=1,point2=2,now=0;
if (root==1) point=2;
now=max(now,ducati[root].d+ducati[point].d+1);
if (root==1) point=2,point2=3;
else if (root==2) point=1,point2=3;
now=max(now,ducati[point].d+ducati[point2].d+2);
now=max(now,maxv);
if (now<ans)
{
ans=now;
poss=root;
}
}
cout<<ans<<endl;
for (int i=1;i<=len;i++)
{
if (i==poss) continue;
cout<<ducati[poss].minrt<<' '<<ducati[i].minrt<<endl;
}
return 0;
}