题意
给定一棵树,选一个节点和根相连,使得最后所有点到根的距离和最小,求最小距离和
思路
首先一个点和根连起来以后,影响的只有根到这个点这条路径上的点,以及他们的子树,并且对于每个点影响的权值都是能够确定的。
一个点减少多少距离,那么子树中的点也会减少多少距离。
从路径的中点以下,因为连了根减少的权值为2-4-6…以此类推
虽然树形DP怎么打都行,但是不知道为什么网上找到的代码都特别复杂,看了半天才看懂。然后发现大部分代码都是没用的。。
因为权值改变是有规律的,所以我们可以用回溯来枚举连接点和抵消变化
- 先做一遍搜索找出每个点的子树大小
- 用sum[dep]的差值表示从父节点到当前节点时,会影响的父节点子树中除自己这支以外的所有点个数
- 从中点枚举到当前点,中点子树中除自己这支以外的所有点个数应该减掉,因为相对父节点来说,距离增加了1
- 从父节点枚举到当前点,应该再加上当前点子树的大小,因为相对父节点来说,距离减少了1
代码
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define rep(i,a,b) for(int i=a;i<b;i++)
#define sc(a) scanf("%d",&a)
const int INF=0x3f3f3f3f;
const int maxn=2e5+50;
const int mod=1e9+7;
const double eps=1e-8;
#define pii pair<int,int>
typedef long long ll;
typedef unsigned int ui;
using namespace std;
int n;
vector<int> G[maxn];
int cnt[maxn];
ll sum[maxn];
ll totDis;
ll gAns;
int dfs1(int fa,int cur,int depth){
int &tot=cnt[cur]=1;
for(int i=0;i<G[cur].size();i++){
int v=G[cur][i];
if(v!=fa)
tot+=dfs1(cur,v,depth+1);
}
//printf("%d %lld %d\n",cur,totDis,tot);
totDis+=depth;
return tot;
}
void dfs2(int cur,int fa,int dep,ll ans){
if(dep) sum[dep]=sum[dep-1]+cnt[fa]-cnt[cur];
//printf("dep %d sum_dep %lld\n",dep,sum[dep]);
int halfDep=(dep+1)/2;
int s=halfDep+1, t=dep;
if(dep>1){
if(dep&1) s--;
ans-=sum[t]-sum[s];
ans+=cnt[cur];
}
//printf("s= %d t= %d u=%d depth=%d ans=%lld\n",s,t,cur, dep, ans);
gAns=max(ans,gAns);
for(int i=0;i<G[cur].size();i++){
int v=G[cur][i];
if(v!=fa)
dfs2(v,cur,dep+1,ans);
}
}
int main()
{
#ifndef ONLINE_JUDGE
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
#endif
int T; scanf("%d",&T);
while(T--){
scanf("%d",&n);
rep(i,1,n+1) {
G[i].clear();
}
int u,v;
rep(i,1,n) {
sc(u); sc(v);
G[u].push_back(v);
G[v].push_back(u);
}
mem(cnt,0); gAns=0;
totDis=0;
dfs1(-1,1,0);
mem(sum,0);
dfs2(1,-1,0,0);
printf("%lld\n",totDis-gAns);
}
return 0;
}