题意:给定n的点,n-2条边,也就是给出了两棵树,要求加一条边连接两棵树并使得连接好的这棵树上任意两点距离和最小。
思路:先取任意一点bfs来分开两棵树,此时假设第一棵树有n1个点,第二棵树有n2个点,分别对这两棵树进行树形dp,dpsum[i]记录在本棵树上其余点到点i的距离和。分别找出两棵树上的最小的dpsum值,此时连接这两个点就是最优解。此时要统计任意点距离和了(后来发现树的重心的性质就是所有点到当前点的距离和最小):
第一种比较暴力的做法,就是直接对这两个点加边,然后跑树上任意两点距离和的板子。(树的重心写法,答案是跑树上任意两点距离和)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+5;
int n,m,t,head[N],sumson[N],tot,vis[N],ji,minNode,minBalance,dian1,dian2;
ll dp[N];
struct p{
int next,to;
}edge[N];
void add(int u,int v){
edge[tot]={head[u],v};
head[u]=tot++;
}
void bfs(){
queue<int>Q;
vis[1]=1;
Q.push(1);
while(!Q.empty()){
int x=Q.front();
Q.pop();
for(int i=head[x];i!=-1;i=edge[i].next){
int to=edge[i].to;
if(vis[to])continue;
vis[to]=1;
Q.push(to);
}
}
}
void dfs(int x,int fa,int nn){
sumson[x]=1; //节点本身
int maxSub=0; //maxSub为节点u的最大子树节点个数
for(int i=head[x];i!=-1;i=edge[i].next){
int to=edge[i].to;
if(to!=fa){
dfs(to,x,nn);
sumson[x]+=sumson[to];
maxSub=max(maxSub,sumson[to]);
}
}
maxSub=max(maxSub,nn-sumson[x]);
if(maxSub<minBalance){
minNode=x;
minBalance=maxSub;
}
}
void dfs2(int x,int fa){
sumson[x]=1;
for(int i=head[x];i!=-1;i=edge[i].next){
int son=edge[i].to;
if(fa==son)continue;
dfs2(son,x);
sumson[x]+=sumson[son];
dp[x]+=dp[son]+1ll*sumson[son]*(n-sumson[son]);
}
}
int main(){
while(~scanf("%d",&n)){
if(n==2){
printf("1\n");
continue;
}
for(int i=0;i<=n+3;i++){
sumson[i]=vis[i]=dp[i]=0;
head[i]=-1;
}
int u,v,n1=0,n2;tot=ji=1;
for(int i=1;i<=n-2;i++){
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
bfs();
for(int i=1;i<=n;i++)n1+=vis[i];
n2=n-n1;
minNode=0,minBalance=0x3f3f3f3f;
dfs(1,0,n1);
dian1=minNode;
minNode=0,minBalance=0x3f3f3f3f;
for(int i=1;i<=n;i++){
if(!vis[i]){
dfs(i,0,n2);
break;
}
}
dian2=minNode;
add(dian1,dian2);
add(dian2,dian1);
dfs2(1,-1);///以任意一点为根进行搜索
printf("%lld\n",dp[1]);
}
return 0;
}
第二种做法我们可以采取算贡献的方式:对于这个两个树,树上任意两点距离和就是每棵树上(dpsum求和/2),再考虑连接两个点的这条边的贡献就是n1*n2因为第一棵树的每个点都要和第二棵树上的每个点算一次距离,然后就是第一棵树上的点在连接第二棵树的时候要在第二棵树上走n1次第二棵树最大的dpsum值,同理第二棵树上的点在连接第一棵树的时候要在第一棵树上走n2次第二棵树最大的dpsum值。因此最终的答案就是ans=ans1+ans2+dpsum[dian1]*n2+dpsum[dian2]*n1+n1*n2;(用两个树形dp求出树上所有点到当前点距离和最小的点,没有套树的重心的板子,答案是公式法得出的,没有跑树上任意两点距离和)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+5;
int n,m,t,head[N],sumson[N],tot,vis[N],ji;
ll dpfa[N],dpson[N],dpsum[N];
struct p{
int next,to;
}edge[N];
void add(int u,int v){
edge[tot]={head[u],v};
head[u]=tot++;
}
void bfs(){
queue<int>Q;
vis[1]=1;
Q.push(1);
while(!Q.empty()){
int x=Q.front();
Q.pop();
for(int i=head[x];i!=-1;i=edge[i].next){
int to=edge[i].to;
if(vis[to])continue;
vis[to]=1;
Q.push(to);
}
}
}
void dfs(int x,int fa){
sumson[x]=1;
dpson[x]=0;
for(int i=head[x];i!=-1;i=edge[i].next){
int to=edge[i].to;
if(to==fa)continue;
dfs(to,x);
sumson[x]+=sumson[to];
dpson[x]+=dpson[to]+sumson[to];
}
}
void dfs2(int x,int fa,int nn){
if(x!=1&&x!=ji){
dpfa[x]=dpsum[fa]-dpson[x]-2*sumson[x]+nn;
dpsum[x]=dpfa[x]+dpson[x];
}
for(int i=head[x];i!=-1;i=edge[i].next){
int to=edge[i].to;
if(to==fa)continue;
dfs2(to,x,nn);
}
}
int main(){
while(~scanf("%d",&n)){
if(n==2){
printf("1\n");
continue;
}
for(int i=0;i<=n+3;i++){
dpson[i]=dpsum[i]=dpfa[i]=sumson[i]=vis[i]=0;
head[i]=-1;
}
int u,v,n1=0,n2;tot=ji=1;
for(int i=1;i<=n-2;i++){
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
bfs();
for(int i=1;i<=n;i++)n1+=vis[i];
n2=n-n1;
dfs(1,0);
dpsum[1]=dpson[1];
dfs2(1,0,n1);
ll maxx=1ll<<60,ans1=0,ans2=0,ans=0;
int dian1,dian2;
for(int i=1;i<=n;i++){
ans1+=dpsum[i];
if(dpsum[i]&&dpsum[i]<maxx){
dian1=i;
maxx=dpsum[i];
}
}
for(int i=1;i<=n;i++){
if(!vis[i]){
ji=i;
break;
}
}
dfs(ji,0);
dpsum[ji]=dpson[ji];
dfs2(ji,0,n2);
maxx=1ll<<60;
for(int i=1;i<=n;i++){
if(!vis[i]&&dpsum[i]<maxx){
dian2=i;
maxx=dpsum[i];
}
}
for(int i=1;i<=n;i++)ans+=dpsum[i];
ans2=ans-ans1;
ans1/=2,ans2/=2,ans/=2;
ans=ans+dpsum[dian1]*n2+dpsum[dian2]*n1+n1*n2;
printf("%lld\n",ans);
}
return 0;
}