P12906 [NERC 2020] Guide 题解

题目链接:https://www.luogu.com.cn/problem/P12906

题目概述

给定一棵有根树(根节点为1),需要找到一条从根节点出发的最短路径,使得路径访问恰好k个不同的节点。路径可以重复访问节点,但只计算第一次访问时的节点。

解题思路

关键观察

  1. 路径结构分析:最优路径通常由两部分组成:

    • 沿着最长链(从根节点到最深叶节点的路径)访问节点

    • 访问一些分支节点,每个分支节点需要额外的往返边

  2. 路径长度公式

    • 如果所有k个节点都在最长链上:路径长度 = k - 1

    • 如果需要访问分支节点:路径长度 = 2(k - L) + (L - 1),其中L是最长链长度

  3. 路径构造策略

    • 优先访问分支节点(需要往返)

    • 最后访问最长链上的节点(单向访问)

算法实现

数据结构

  • 使用邻接表存储树结构

  • 记录每个节点的深度

  • 标记最长链上的节点

  • 记录已访问的节点

核心步骤

  1. 深度计算:通过DFS计算每个节点的深度,并找到最大深度

  2. 标记最长链:从深度最大的节点回溯到根节点,标记路径上的节点

  3. 路径构造:通过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;
    }
}
相关推荐
你撅嘴真丑14 分钟前
第八章 - 贪心法
开发语言·c++·算法
VT.馒头19 分钟前
【力扣】2625. 扁平化嵌套数组
前端·javascript·算法·leetcode·职场和发展·typescript
wanghu202420 分钟前
AT_abc443_C~E题题解
c语言·算法
u01092727128 分钟前
模板元编程调试方法
开发语言·c++·算法
2401_838472511 小时前
C++图形编程(OpenGL)
开发语言·c++·算法
-dzk-1 小时前
【代码随想录】LC 203.移除链表元素
c语言·数据结构·c++·算法·链表
进击的小头1 小时前
陷波器实现(针对性滤除特定频率噪声)
c语言·python·算法
知无不研1 小时前
冒泡排序算法
算法·冒泡排序·排序
毅炼1 小时前
hot100打卡——day17
java·数据结构·算法·leetcode·深度优先
Tisfy1 小时前
LeetCode 3010.将数组分成最小总代价的子数组 I:排序 OR 维护最小次小
算法·leetcode·题解·排序·最小次小值