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 ∑ni=1∑nj=i+1dis(i,j), where 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
给定两棵树,n个节点,n-2条边。现在需要在两棵树中连一条边并令式子的值最小。
首先我们要先知道一个概念,树的重心。对于这个题,树中边的权值设为1即可。
树的重心的性质:
树中所有点到某个点的距离和中,到重心的距离和是最小的,如果有两个距离和,他们的距离和一样。
那么这个题我们要做的就是寻找到两棵树的重心,将重心连起来,然后算距离和。算距离和的时候,我们可以单独考虑每条边对距离和的贡献,因为有一个公式可以方便的求距离和
任意一条边对距离和的贡献 = 左端结点个数 * 右端结点个数 * 边权
这样,新加边的贡献便是一个固定值了。
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
typedef long long ll;
int n;
ll fa[maxn],si[maxn],f[maxn],g[maxn];
vector< vector<int> >ve;
void dfs(int x)
{
si[x]=1;//至少一个点,即它本身
f[x]=0;//初始化贡献为0
for(auto i:ve[x])//遍历与它联通的点
if(i != fa[x])//若该节点是它的子节点
{
fa[i]=x;//记录该节点的父节点
dfs(i);
si[x]+=si[i];//将子树的节点个数加上
f[x]+=f[i]+si[i]*(n-si[i]);//加上子树的贡献和这条边的贡献
}
}
ll dfs(int x,int root,ll num)
{
if(x != root)//若不是根节点,计算非子树边的贡献
g[x]=g[fa[x]]+f[fa[x]]-f[x]+(n-si[x]-num)*(si[x]+num)-si[x]*(n-si[x]);
else g[x]=0;//为根节点,没有非子树边
ll ans=g[x]+f[x];//子树边+非子树边即为树中所有点到该点的距离和
for(auto i:ve[x])
if(i != fa[x])
ans=min(ans,dfs(i,root,num));
return ans;
}
int main()
{
scanf("%d",&n);//输入
ve.resize(n+1);
for(int i=0;i<=n;i++)//初始化父亲节点为-1
fa[i]=-1;
for(int i=1,x,y;i<n-1;i++)//输入
{
scanf("%d%d",&x,&y);
ve[x].push_back(y);
ve[y].push_back(x);
}
int root2,root1=1;//两棵树的根节点
fa[1]=1;//令1号节点为根节点
dfs(1);//搜索1号节点所在的树
ll sonNum=si[1];//第一棵树的节点个数
ll fSonNum=n-sonNum;//另一棵树的节点个数
for(int i=1;i<=n;i++)//寻找第二棵树的节点
{
if(fa[i] == -1)//找到了第二棵树的节点
{
fa[i]=i;//令该节点为此树的根节点
root2=i;//记录该树的根节点
dfs(i);
break;
}
}
cout<<dfs(1,1,fSonNum)+dfs(root2,root2,sonNum)+sonNum*fSonNum<<endl;
}