【树\思维】P1395 会议
题目描述
有一个村庄居住着 nnn 个村民,有 n−1n-1n−1 条路径使得这 nnn 个村民的家联通,每条路径的长度都为 111。现在村长希望在某个村民家中召开一场会议,村长希望所有村民到会议地点的距离之和最小,那么村长应该要把会议地点设置在哪个村民的家中,并且这个距离总和最小是多少?若有多个节点都满足条件,则选择节点编号最小的那个点。
输入格式
第一行,一个数 nnn,表示有 nnn 个村民。
接下来 n−1n-1n−1 行,每行两个数字 aaa 和 bbb,表示村民 aaa 的家和村民 bbb 的家之间存在一条路径。
输出格式
一行输出两个数字 xxx 和 yyy。
xxx 表示村长将会在哪个村民家中举办会议。
yyy 表示距离之和的最小值。
输入输出样例
输入 #1
4
1 2
2 3
3 4
输出 #1
2 4
说明/提示
对于 70%70\%70% 数据 n≤103n \le 10^3n≤103。
对于 100%100\%100% 数据 n≤5×104n \le 5 \times 10^4n≤5×104。
思路
考虑以节点 111 为根节点建立树,并用 dfs
求出每个节点子树的节点个数,记作 sonison_isoni,同步可以求出以 111 为会议地点所需的距离总和,只需要在回溯时将 ansdis←ansdis+sonians_{dis} \gets ans_{dis}+son_iansdis←ansdis+soni 即可,代表所有属于该节点为根的子树的节点全部需要多走 111 个单位长度才能到达该节点的父亲节点。
我们考虑会议地址不在 111 上的情况,不妨令 ttt 为 111 的其中一个子节点,则容易发现,当我们把会议地址从 111 换到 ttt 的时候, ttt 所有子树上的节点到会议地址的距离都少了 111,整个树上不在以 ttt 为根的子树上的其他节点到会议地址的距离都加了 111,故转移可以写为:
ansdis=min(ansdis,ansdis+sont−(son1−sont))ans_{dis}=\min(ans_{dis},ans_{dis} +son_t-(son_1-son_t))ansdis=min(ansdis,ansdis+sont−(son1−sont))
对于子树转移同理,只需要运行 222 次 dfs
算法,每次都遍历一整个大小为 nnn 的树,总共时间复杂度为 O(n)O(n)O(n)。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,head[50005],nex[100005],to[100005],cnt = 0;
void add(int x,int y) {nex[++cnt] = head[x];head[x] = cnt;to[cnt] = y;
}
int son[50005];
int ans_point,ans_dis;
void dfs(int now,int fa) {son[now]++;for(int i = head[now];i;i = nex[i]) {if(to[i] != fa) {dfs(to[i],now);son[now] += son[to[i]];ans_dis += son[to[i]];}}
}
void find_ans(int now,int fa,int dis) {if(now != 1) {dis -= son[now];dis += (son[1] - son[now]);if(dis < ans_dis or (dis == ans_dis and ans_point > now)) {ans_dis = dis,ans_point = now;}}for(int i = head[now];i;i = nex[i]) {if(to[i] != fa) {find_ans(to[i],now,dis);}}
}
signed main() {scanf("%lld",&n);for(int i = 1;i < n;i++) {int x,y;scanf("%lld %lld",&x,&y);add(x,y),add(y,x);}dfs(1,1);ans_point = 1;find_ans(1,1,ans_dis);printf("%lld %lld\n",ans_point,ans_dis);return 0;
}