1 条题解

  • 0
    @ 2025-10-29 17:05:13

    星辰瞬移路径规划问题题解

    一、题目分析

    1. 问题核心

    • 目标:从起点星辰s出发,通过n-1次瞬移遍历所有n颗星辰,选择每次瞬移的方向(向左/向右),使总能量消耗最小,并输出具体路径。
    • 关键约束:第i次瞬移的成本由方向决定,向左为l[i],向右为r[i];路径需包含所有星辰,且起点固定为s
    • 数据规模n最大可达5e5,要求算法时间复杂度为O(n),否则会超时。

    2. 问题本质

    该问题可转化为“带方向成本的线性遍历路径构造”:星辰沿直线排列,每次瞬移的方向选择直接影响成本,需在保证遍历所有星辰的前提下,通过贪心策略选择最小成本方向,并高效构造路径。

    二、算法思路

    1. 核心策略:贪心选择方向

    贪心的核心逻辑是“每次瞬移优先选成本更低的方向”,但需处理一个特殊约束:若起点s不是最右侧星辰,前s次瞬移中至少要有一次向左(否则无法遍历左侧星辰)。具体步骤如下:

    1. 初始方向统一:若第一次瞬移的向右成本更低(l[1] > r[1]),通过反转节点编号(x = n - x + 1),将问题转化为“第一次优先向左”的统一场景,简化代码逻辑。
    2. 标记方向选择:对第i次瞬移,用c[i]标记方向——c[i] = 1表示选向右(r[i] < l[i]),c[i] = 0表示选向左(l[i] ≤ r[i])。
    3. 处理特殊约束:若s ≠ n且前s次瞬移均未选向左(c[1..s]全为0),需在这s次中选“成本差最小”的一次强制改为向左(c[d] = 1),确保能遍历左侧星辰。

    2. 路径构造:双端队列高效维护

    路径构造的关键是利用“连续方向批量处理”,用双端队列(deque)维护未访问节点,根据连续的方向批量弹出节点,避免逐个处理的低效:

    1. 初始化队列:将除起点s外的所有星辰加入双端队列p,顺序为1,2,...,s-1,s+1,...,n
    2. 批量处理连续方向
      • 找到连续相同方向的区间(如ijc值均相同),一次性处理该区间的所有瞬移。
      • 若方向为向左(c[i] = 0):从队列头部弹出节点(左侧未访问节点),加入路径。
      • 若方向为向右(c[i] = 1):从队列尾部弹出节点(右侧未访问节点),加入路径。
    3. 恢复反转:若之前进行了节点编号反转,将路径中的所有节点编号反转回原编号,得到最终路径。

    3. 总成本计算

    遍历所有n-1次瞬移,根据c[i]累加成本:c[i] = 1时加r[i]c[i] = 0时加l[i]

    三、代码逐段解析

    1. 输入处理与初始方向统一

    cin >> n >> s;
    for (int i = 1; i < n; i++) cin >> l[i] >> r[i];
    
    // 若第一次瞬移向右成本更低,反转节点编号,统一处理逻辑
    if (l[1] > r[1]) {
        rev = 1;
        s = n - s + 1;  // 起点反转
        for (int i = 1; i < n; i++) swap(l[i], r[i]);  // 成本反转(原向右变向左,反之亦然)
    }
    
    • 作用:将“第一次优先向右”的场景转化为“第一次优先向左”,避免后续处理分情况讨论,简化代码。

    2. 方向选择与特殊约束处理

    // 标记每次瞬移的最优方向
    for (int i = 1; i < n; i++) c[i] = r[i] < l[i];
    
    // 处理s≠n时,前s次需至少一次向左的约束
    if (s != n) {
        int ok = 0;
        // 检查前s次是否已有向左的选择
        for (int i = 1; i <= s; i++) if (c[i]) ok = 1;
        if (!ok) {
            // 选成本差最小的一次强制向左
            int mn = 1e18, d = -1;
            for (int i = 1; i <= s; i++) {
                if (r[i] - l[i] < mn) {
                    mn = r[i] - l[i];
                    d = i;
                }
            }
            c[d] = 1;
        }
    }
    
    • 关键逻辑c[i] = r[i] < l[i]直接体现贪心选择——成本低的方向优先;特殊约束处理确保路径能覆盖左侧星辰,避免“卡死”。

    3. 双端队列构造路径

    vector<int> ans;
    ans.push_back(s);  // 路径起点
    deque<int> p;
    // 初始化未访问节点队列
    for (int i = 1; i <= n; i++) if (i != s) p.push_back(i);
    
    // 批量处理连续方向的瞬移
    for (int i = 1, j; i <= n - 1; i = j + 1) {
        j = i;
        // 找到连续相同方向的区间[i, j]
        while (j + 1 <= n - 1 && c[j + 1] == c[j]) j++;
        
        if (!c[i]) {  // 方向向左:从队列头部取节点(左侧未访问)
            for (int k = j - i; k >= 0; k--) ans.push_back(p[k]);
            for (int k = j - i; k >= 0; k--) p.pop_front();
        } else {  // 方向向右:从队列尾部取节点(右侧未访问)
            for (int k = p.size() - (j - i + 1); k < p.size(); k++) ans.push_back(p[k]);
            for (int k = j - i; k >= 0; k--) p.pop_back();
        }
    }
    
    // 恢复反转的节点编号
    if (rev) for (auto &x : ans) x = n - x + 1;
    
    • 效率优势:双端队列的push_back/pop_front/pop_back操作均为O(1),批量处理连续方向使整体路径构造时间为O(n),适合大规模数据。

    4. 输出总成本与路径

    // 计算总成本
    int w = 0;
    for (int i = 1; i < n; i++) w += c[i] ? r[i] : l[i];
    
    // 输出结果
    cout << w << "\n";
    for (int i = 0; i < n; i++) cout << ans[i] << " \n"[i + 1 == n];
    

    四、关键细节与易错点

    1. 节点编号反转的正确性:反转后,“向左”和“向右”的含义也随之反转,因此需同时交换l[i]r[i],确保成本对应正确。
    2. 特殊约束的必要性:若s ≠ n且前s次均向右,会导致左侧星辰无法遍历(起点s左侧有s-1颗星辰,需至少一次向左才能到达),必须强制调整一次方向。
    3. 双端队列的批量处理:需准确计算连续方向区间的长度(j - i + 1),避免多取或少取节点,确保路径包含所有星辰。

    五、样例验证

    以样例输入为例,逐步验证算法逻辑:

    • 输入n=4, s=2l=[5,4,2]r=[3,6,2]
    • 初始方向统一l[1]=5 > r[1]=3,需反转:
      • 反转后s = 4 - 2 + 1 = 3lr交换,变为l=[3,6,2]r=[5,4,2]
    • 标记方向
      • c[1]r[1]=5 > l[1]=3c[1]=0(向左)。
      • c[2]r[2]=4 < l[2]=6c[2]=1(向右)。
      • c[3]r[3]=2 == l[3]=2c[3]=0(向左)。
    • 特殊约束处理s=3 ≠ 4,检查前3c值(0,1,0),已有向左,无需调整。
    • 路径构造
      • 未访问节点队列p = [1,2,4];起点ans = [3]
      • 处理i=1c[1]=0,连续区间[1,1]):从头部取1个节点1ans = [3,1]p = [2,4]
      • 处理i=2c[2]=1,连续区间[2,2]):从尾部取1个节点4ans = [3,1,4]p = [2]
      • 处理i=3c[3]=0,连续区间[3,3]):从头部取1个节点2ans = [3,1,4,2]
    • 恢复反转:将节点编号反转(x=4-x+1),ans变为[2,4,1,3],与样例输出一致。
    • 总成本c[1]=0(加l[1]=3)、c[2]=1(加r[2]=4)、c[3]=0(加l[3]=2),总3+4+2=9,符合样例。

    六、学习建议

    1. 贪心策略理解:重点掌握“为何贪心选择方向可行”——由于每次瞬移的成本仅与次数和方向有关,与当前位置无关,因此局部最优(每次选低成本方向)可导向全局最优。
    2. 数据结构应用:学习双端队列在“两端取元素”场景中的高效性,对比数组或普通队列的劣势(如数组头部删除为O(n))。
    3. 边界处理:关注特殊情况(如s=1s=nl[i]=r[i]),理解代码中针对这些情况的处理逻辑,避免边界错误。
    • 1

    信息

    ID
    4369
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者