Cotree
Time Limit: 2000/1000 MS (Java/Others)
Memory Limit: 262144/262144 K (Java/Others)
Problem Description
Avin has two trees which are not connected. He asks you to add an edge between them to make them connected while minimizing the function ∑ i = 1 n ∑ j = i + 1 n d i s ( i , j ) \sum_{i=1}^{n}\sum_{j=i+1}^{n}dis(i,j) ∑i=1n∑j=i+1ndis(i,j) , where d i s ( i , j ) dis(i,j) dis(i,j) represents the number of edges of the path from i to j . He is happy with only the function value.
Input
The first line contains a number n (2<=n<=100000). In each of the following n−2 lines, there are two numbers u and v, meaning that there is an edge between u and v. The input is guaranteed to contain exactly two trees.
Output
Just print the minimum function value.
Sample Input
3
1 2
Sample Output
4
Tips
题意:
给定一个具有 n n n 个结点、 n − 2 n-2 n−2 条边的图,保证其连通分量为 2 2 2 。现在请你添加一条边,使图连通,求连通图上任意两个结点的距离之和的最小值。
题解:
举几个例子就会发现,我们应该选择两棵树的树根将它们相连,且这两个树根使得这两棵树尽可能地“平衡”。事实上,这样的根节点叫做树的重心。其形式化的定义为:
以某结点为根节点的一棵或几棵子树中,结点个数的最大值称为该结点的 balance \text{balance} balance ,重心的 balance \text{balance} balance 是所有结点中最小的那个。
要找到树的重心,只需一遍 dfs \text{dfs} dfs ,统计出每个结点的子树分别含有多少结点,得到 balance \text{balance} balance ,选出 balance \text{balance} balance 最小的那个结点即可。找出两个重心以后,我们将它们相连,这就完成了这一题的第一部分(当然,在对树进行操作之前,还需要用并查集取出两个分别位于两棵树的结点)。
而本题的剩余部分,在于如何求解一棵树上任意两个结点的距离之和。区别于暴力,较优的解法为,考虑每条边被经过的次数,最后的答案即为次数乘以边权(本题为 1 1 1)的和。一条边被经过的次数,等于由它连接的两部分的结点的个数之积。而结点个数的求解方法和上述 dfs \text{dfs} dfs 函数一致。
因此,总结一下本题的解决过程就是:首先用并查集找出分别位于两棵树中的两个结点,然后对这两个结点分别进行 dfs \text{dfs} dfs ,找出两棵树的重心,将重心连接,再 dfs \text{dfs} dfs 一遍,最后计算出答案并输出即可。
Reference Code
#include <cstdio>
#include <cstring>
#include <cstring>
#include <algorithm>
using std::max;
typedef long long ll;
const int INF=0x3f3f3f3f;
const int MAXN=1e5+10;
const int MAXM=2e5+10;
int fa[MAXN],hi[MAXN];
int cnt[MAXN];
int Find(int v){
return (fa[v]==v)?(v):(fa[v]=Find(fa[v]));
}
inline void Union(int a,int b){
int r1=Find(a),r2=Find(b);
if (r1==r2) return;
if (hi[r1]<hi[r2]){
fa[r1]=r2;
cnt[r2]+=cnt[r1];
}
else if (hi[r1]>hi[r2]){
fa[r2]=r1;
cnt[r1]+=cnt[r2];
}
else{
fa[r1]=r2;
cnt[r2]+=cnt[r1];
++hi[r2];
}
}
struct Edge{
int to,next;
}edge[MAXM];
int tot,head[MAXN];
inline void init(){
tot=0;
memset(head,-1,sizeof(head));
for (int i=0;i<MAXN;++i){
fa[i]=i;
cnt[i]=1;
}
}
inline void add_edge(int u,int v){
edge[tot]=(Edge){v,head[u]};
head[u]=tot++;
edge[tot]=(Edge){u,head[v]};
head[v]=tot++;
}
int dp[MAXN];
int balance,root;
void dfs(int u,int pre,int n){
dp[u]=1;
int balan=0;
for (int i=head[u];~i;i=edge[i].next){
int v=edge[i].to;
if (v==pre) continue;
dfs(v,u,n);
dp[u]+=dp[v];
balan=max(balan,dp[v]);
}
balan=max(balan,n-dp[u]);
if (balan<balance){
balance=balan;
root=u;
}
}
int set_root(int rt,int n){
balance=INF;
dfs(rt,-1,n);
return root;
}
int n;
int u,v;
int r1,n1,r2,n2;
int main(){
init();
scanf("%d",&n);
for (int i=2;i<n;++i){
scanf("%d%d",&u,&v);
Union(u,v);
add_edge(u,v);
}
r1=Find(1);
for (int i=2;i<=n;++i){
if (Find(i)!=r1)
r2=Find(i);
}
n1=cnt[r1];
n2=n-cnt[r1];
r1=set_root(r1,n1);
r2=set_root(r2,n2);
add_edge(r1,r2);
dfs(r1,-1,n1);
ll res=0;
for (int i=1;i<=n;++i)
res+=(ll)dp[i]*(n-dp[i]);
printf("%lld\n",res);
return 0;
}