题目链接:https://www.luogu.com.cn/problem/P12906
题目概述
给定一棵有根树(根节点为1),需要找到一条从根节点出发的最短路径,使得路径访问恰好k个不同的节点。路径可以重复访问节点,但只计算第一次访问时的节点。
解题思路
关键观察
-
路径结构分析:最优路径通常由两部分组成:
-
沿着最长链(从根节点到最深叶节点的路径)访问节点
-
访问一些分支节点,每个分支节点需要额外的往返边
-
-
路径长度公式:
-
如果所有k个节点都在最长链上:路径长度 = k - 1
-
如果需要访问分支节点:路径长度 = 2(k - L) + (L - 1),其中L是最长链长度
-
-
路径构造策略:
-
优先访问分支节点(需要往返)
-
最后访问最长链上的节点(单向访问)
-
算法实现
数据结构
-
使用邻接表存储树结构
-
记录每个节点的深度
-
标记最长链上的节点
-
记录已访问的节点
核心步骤
-
深度计算:通过DFS计算每个节点的深度,并找到最大深度
-
标记最长链:从深度最大的节点回溯到根节点,标记路径上的节点
-
路径构造:通过DFS构造路径,先访问分支节点后访问最长链节点
代码实现
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e2+7;
vector<int>g[N];
int dep[N],sum,mx;
int n,k;
bool vis[N],vis1[N];
// 计算节点深度和最大深度
void dfs(int u,int fa){
for(auto v:g[u]){
if(v==fa)continue;
dep[v]=dep[u]+1;
mx=max(mx,dep[v]);
dfs(v,u);
}
}
// 标记最长链上的节点
bool dfs1(int u,int fa){
if(dep[u]==mx){
vis[u]=1;
return 1;
}
for(auto v:g[u]){
if(v==fa)continue;
if(dfs1(v,u)){
vis[u]=1;
return 1;
}
}
return 0;
}
// 构造并输出路径
void dfs2(int u,int fa){
cout<<u<<" ";
if(!vis[u]&&!vis1[u]){
sum++;
vis1[u]=1;
}
for(auto v:g[u]){
if(sum-1>=k-mx)break; // 分支节点数达到要求时停止
if(v==fa)continue;
if(vis[v])continue; // 跳过最长链节点
dfs2(v,u);
cout<<u<<" "; // 回溯
}
for(auto v:g[u]){
if(vis[v]){
dfs2(v,u); // 访问最长链节点
break;
}
}
}
signed main(){
int t;cin>>t;
while(t--){
cin>>n>>k;
// 初始化
for(int i=1;i<=n;i++){
g[i].clear();
dep[i]=0;
vis[i]=0;
vis1[i]=0;
}
// 建树
for(int i=2;i<=n;i++){
int u;cin>>u;
g[u].push_back(i);
}
// 计算深度和最长链
dep[1]=1;
sum=1;
mx=1; // 关键:初始化mx=1,处理只有根节点的情况
dfs(1,0);
// 计算路径长度
int ans=0;
if(mx>=k)ans=k-1;
else ans=2*(k-mx)+mx-1;
cout<<ans<<endl;
// 调整mx,确保不超过k
mx=min(mx,k);
// 标记最长链并输出路径
dfs1(1,0);
dfs2(1,0);
cout<<endl;
}
}