题意
给一棵树加一条边,使得根到所有点的距离之和最小,问最小的距离之和是多少。
题解
一道很考思维的树形DP。首先的话,先用简单DFS求出每个点的子节点数量和以及总路径长度。然后再尝试加边,计算加边以后能减少的距离。最后总路径长度-减少最大的距离=所求长度。
计算减少距离利用的是DFS的思想,对于细节的考量还是比较详细的。首先的话就是对于中点的划分。中点选取dep/2+1作为中点,可以证明dep-(dep/2)+1<=dep/2+1(可以取到等号)。因此中点选择dep/2+1是刚好合适的。另外的话,我们可以计算出来每一层的点的个数。在计算能减少的距离的时候,首先要减去(这一层的点的个数-中点层点的个数),因为从中点以下(包括中点)到该连接点(不包括该点),距离都会因为连接点的变动而+1。连接点以下的点(包括连接点),距离都会-1。计算能减少的距离,与最大减少距离进行比较记录。并将这个距离传给下一层即可。
注意事项
有个细节需要注意一下。当初为了方便,我直接把记录减少距离的变量设置为了全局变量,而不是参数。后来才意识到,这样是绝对不可以的,因为这个距离变量是按照层进行传递的,而不是针对一个全局变量进行修改。
代码
#include <iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cmath>
#include<queue>
#include<string>
#include<set>
#include<map>
#include<bitset>
#include<stack>
#include<string>
#define UP(i,l,h) for(int i=l;i<h;i++)
#define DOWN(i,h,l) for(int i=h-1;i>=l;i--)
#define W(a) while(a)
#define MEM(a,b) memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define LL long long
#define MAXN 200010
#define EPS 1e-10
using namespace std;
vector<int> vc[MAXN];
int num[MAXN],f[MAXN];
LL allans,total;
int dfs(int u,int pa,int deep){
num[u]=1;
int sz=vc[u].size();
UP(i,0,sz){
int x=vc[u][i];
// cout<<x<<" "<<vis[x]<<endl;
if(x!=pa){
num[u]+=dfs(x,u,deep+1);
}
}
total+=deep;
return num[u];
}
void dfs2(int deep,int u,int pa,LL ans){
if(deep){
f[deep]=f[deep-1]+num[pa]-num[u];
// cout<<"xxx"<<p<<" "<<num[p]<<" "<<num[u]<<endl;
}
int mid=deep/2+1;
if(deep>1){
ans-=(f[deep]-f[mid]);
ans+=num[u];
}
// cout<<mid<<" "<<f[mid]<<endl;
allans=max(ans,allans);
int sz=vc[u].size();
UP(i,0,sz){
int x=vc[u][i];
if(x!=pa)
dfs2(deep+1,x,u,ans);
}
}
int main(){
int t;
scanf("%d",&t);
W(t--){
MEM(vc,0);
MEM(num,0);
MEM(f,0);
total=0;
allans=0;
int n;
scanf("%d",&n);
int a,b;
UP(i,0,n-1){
scanf("%d%d",&a,&b);
vc[a].push_back(b);
vc[b].push_back(a);
}
dfs(1,-1,0);
dfs2(0,1,-1,0);
printf("%lld\n",total-allans);
}
}