http://acm.hdu.edu.cn/showproblem.php?pid=4607
思路:我们可以这样考虑,假设我们走的起点为S,终点为T,那么路径S-T上的点我们必须都走到(因为是一棵树),那么如果路径S-T上的点大于或等于k,则我们只需要沿着路径一直走就行,所以最小长度为k-1,否则,我们一定是在路径上的一些点停下,然后转向其他分支然后再回来,此时才能满足一共走过k个点。假设从S到T的路径上一共有num个点,则最后我们要求的答案ans=num-1+?,这个?即为中间点(可以包括S和T)到别的分支再回来所要走的最小距离,因为每个分支互不影响,所以我们可以对每一个分支分开求,因为图为一棵树,所以我们即是求一棵子树从根节点到他的子节点中遍历x点再回来所要走的最短距离,我们可以有观察加数学归纳法证明这个值为2*x,即从一棵树的根结点往他的子树遍历x个点再回来的最小距离为2*x,所以?=2*(k-num),所以答案ans=num-1+(k-num)*2,要是这个值最小,则要使num最大,所以我们只要求得该树的直径即可,以下时代码,求树的直径是个很经典的问题了,这里直接给出代码:
#include <iostream> #include <string.h> #include <stdio.h> #define maxn 100100 using namespace std; struct edge { int to; int next; }e[maxn<<1]; int box[maxn],cnt; void init() { cnt=0; memset(box,-1,sizeof(box)); } void add(int from,int to) { e[cnt].to=to; e[cnt].next=box[from]; box[from]=cnt++; } int vis[maxn],dist[maxn]; void dfs(int now) { int t,v; for(t=box[now];t+1;t=e[t].next) { v=e[t].to; if(dist[v]==-1) { dist[v]=dist[now]+1; dfs(v); } } } int main() { // freopen("dd.txt","r",stdin); int ncase; scanf("%d",&ncase); while(ncase--) { int n,m; scanf("%d%d",&n,&m); init(); int i,j,x,y; for(i=1;i<n;i++) { scanf("%d%d",&x,&y); add(x,y); add(y,x); } memset(dist,-1,sizeof(dist)); dist[1]=0; dfs(1); int root,ma=0; for(i=1;i<=n;i++) { if(dist[i]>ma) { ma=dist[i]; root=i; } } memset(dist,-1,sizeof(dist)); dist[root]=0; dfs(root); ma=0; for(i=1;i<=n;i++) { if(dist[i]>ma) { ma=dist[i]; } } ma++; while(m--) { int k; scanf("%d",&k); if(ma>=k) printf("%d\n",k-1); else { printf("%d\n",ma+(k-ma)*2-1); } } } return 0; }