题目大意:一棵树上有n个标记点,有个人要在一个点出发走遍所有标记点,问从哪个点走(满足后一个条件下要最小),至少走多远。
解题思路:先随便找个标记点DFS遍历,求出每次返回起点时所需的路程s(每条路走2遍,所以每次加2),顺便删掉非标记的叶子节点。
但因为并不需要返回起点,所以一条最长的路径可以不返回。
那就是树的直径,用两遍BFS或DFS就可以求出了。
具体做法为:第一次BFS/DFS用一个任意点,寻找离这个点最远的点,第二次BFS/DFS用这个最远的点,寻找离它最远的点,它们的距离就是树的直径d。
原理与证明过程如下:
最后的答案就是$s-d$了。
最小的出发点就是两次BFS/DFS求出的点,较小的那个。
C++ Code:
#include<cstdio> using namespace std; int n,m,t; int head[123500],cnt=0; struct edge{ int to,next; }a[123500*2]; bool b[123500]={false}; int dis[123500]={0}; void addedge(int x,int y){ cnt++; a[cnt].to=y; a[cnt].next=head[x]; head[x]=cnt; cnt++; a[cnt].to=x; a[cnt].next=head[y]; head[y]=cnt; } void dfs(int k,int pre){ for(int i=head[k];i;i=a[i].next) if(a[i].to!=pre){ int p=a[i].to; dfs(p,k); if(b[p])t+=2; b[k]|=b[p]; } } void dfs2(int k,int pre,int s){ dis[k]=s; for(int i=head[k];i;i=a[i].next) if(a[i].to!=pre) dfs2(a[i].to,k,s+1); } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<n;i++){ int x,y; scanf("%d%d",&x,&y); addedge(x,y); } int x; for(int i=1;i<=m;i++){ scanf("%d",&x); b[x]=true; } t=0; dfs(x,x); dis[0]=-1; int l=0; dfs2(x,x,0); for(int i=1;i<=n;i++) if(b[i]&&dis[i]>dis[l])l=i; int r=0; dfs2(l,l,0); for(int i=1;i<=n;i++) if(b[i]&&dis[i]>dis[r])r=i; t-=dis[r]; printf("%d %d ",l<r?l:r,t); return 0; }