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
, 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
思路:
树中所有点到某个点的距离和中,到重心的距离和是最小的;
如果有两个重心,那么他们的距离和一样
两次dfs树形dp找到两颗树的重心
把两个重心连接起来
再dfs树形dp一次计算答案
计算树上任意两点的距离和,转化为计算每条边被经过的次数乘上边权
每条边被经过的次数等于这条边分隔开的
两端点分别所在的连通块的大小相乘
最后一次dfs树形dp的过程中
假设u是v的父节点,
以v为根的子树大小为sz[v]
则连接他们的边被经过的次数为(n-sz[v])*sz[v]
code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<algorithm>
#include<stack>
using namespace std;
#define ll long long
const int inf=1e9;
const int maxm=1e5+5;
int head[maxm],nt[maxm<<1],to[maxm<<1],cnt;
int mark[maxm];
int sz[maxm],son[maxm];
int num;//第一个连通块的大小
int size;//第二个连通块的大小
int root1,root2;
int n;
ll ans;
void init(){
memset(head,0,sizeof head);
memset(mark,0,sizeof mark);
cnt=1;
num=0;
ans=0;
}
void add(int x,int y){
cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;
}
void dfs(int x){
num++;//计算连通块大小
sz[x]=1;
son[x]=0;
mark[x]=1;
for(int i=head[x];i;i=nt[i]){
int v=to[i];
if(mark[v])continue;
dfs(v);
sz[x]+=sz[v];
son[x]=max(son[x],sz[v]);
}
}
void dfs2(int x){
sz[x]=1;
son[x]=0;
mark[x]=1;
for(int i=head[x];i;i=nt[i]){
int v=to[i];
if(mark[v])continue;
dfs2(v);
sz[x]+=sz[v];
son[x]=max(son[x],sz[v]);
}
son[x]=max(son[x],size-sz[x]);
if(son[x]<son[root2]){
root2=x;
}
}
void dd(int x,int fa){
sz[x]=1;
for(int i=head[x];i;i=nt[i]){
int v=to[i];
if(v==fa)continue;
dd(v,x);
sz[x]+=sz[v];
ans+=(ll)(n-sz[v])*sz[v];//计算答案
}
}
signed main(){
while(scanf("%d",&n)!=EOF){
init();
for(int i=1;i<=n-2;i++){
int a,b;
scanf("%d%d",&a,&b);
add(a,b);
add(b,a);
}
dfs(1);//dfs求第一个连通块的大小且初步计算每个点子树大小
son[root1=0]=inf;//这个地方开始错写成sz[root=0]=inf,wa了几次才找出来
for(int i=1;i<=n;i++){//找第一个连通块的重心
if(mark[i]){//标记的在刚刚遍历的连通块里面
son[i]=max(son[i],num-sz[i]);
if(son[i]<son[root1]){
root1=i;
}
}
}
size=n-num;//另外一个连通块的大小
for(int i=1;i<=n;i++){
if(!mark[i]){//没被标记的在另外一个连通块
son[root2=0]=inf;
dfs2(i);//dfs找第二个连通块的重心
break;
}
}
add(root1,root2);//两个重心连接
add(root2,root1);
dd(1,0);//计算答案
cout<<ans<<endl;
}
return 0;
}