1 条题解

  • 0
    @ 2025-11-19 15:18:59

    题解:A Light Inconvenience(火炬传递与动态队列维护)

    一、题目核心分析

    1. 问题本质

    维护一个动态变化的演员队列(支持从右侧加入/离开),核心要求如下:

    • 初始状态:1 位演员,火炬点燃;
    • 每次队列变化后(加入/离开 p 人),需指定 t(满足 0 ≤ t ≤ 5p,且尽可能小),让所有点燃的火炬向右传递 t 距离(即演员 i 点燃当且仅当 [max(i-t,1), i] 中有点燃的火炬);
    • 最终需保留一组点燃的火炬,满足:
      1. 最右侧演员的火炬必须点燃;
      2. 点燃的火炬数 ≤ 150;
      3. 保留的火炬均需在传递后被点燃。

    2. 关键挑战

    • 队列规模极大(N ≤ 1e17),无法直接模拟每个演员;
    • t 需严格满足 t ≤ 5p(满分要求 t ≤ p),且需最小化;
    • 需动态维护点燃的火炬集合,确保规模≤150,同时覆盖队列所有位置(传递后)。

    3. 核心思路:贪心间隔火炬策略

    通过稀疏化点燃的火炬集合,让相邻点燃火炬的间隔不超过 p(或 5p),从而保证:

    • 传递 t=p 时,所有位置都能被左侧最近的点燃火炬覆盖;
    • 火炬集合规模控制在 O(log N) 级别(远小于 150)。

    具体策略:

    • 始终保留最右侧火炬(强制要求);
    • 从最右侧向左,按“间隔不超过 p”的规则选择前一个点燃火炬,直到队列左端;
    • 这样选择的火炬集合,传递 t=p 时可覆盖全队列,且规模极小。

    二、完整代码

    #include <bits/stdc++.h>
    #include "light.h"
    typedef long long ll;
    using namespace std;
    
    ll current_N = 1;                  // 当前队列总人数
    vector<ll> burning_torches = {1};  // 当前点燃的火炬位置(严格递增)
    
    void prepare() {
        // 初始状态:1人,火炬点燃,无需额外初始化
    }
    
    // 核心处理函数:无论加入还是离开,统一维护点燃的火炬集合
    pair<ll, vector<ll>> process(ll p) {
        vector<ll> new_burning;
        ll rightmost = current_N;  // 最右侧必须点燃,先加入集合
        new_burning.push_back(rightmost);
        
        ll current = rightmost;
        // 从右向左贪心选择点燃的火炬,间隔不超过 p
        while (current > 1) {
            // 前一个点燃火炬的位置:最多向左移动 p 步(确保间隔 ≤ p)
            ll prev = max(1LL, current - p);
            new_burning.push_back(prev);
            current = prev;
        }
        
        // 反转得到严格递增的顺序(从左到右)
        reverse(new_burning.begin(), new_burning.end());
        // 更新当前点燃的火炬集合
        burning_torches = new_burning;
        // 返回 t=p(满足满分要求 t≤p)和火炬集合
        return {p, burning_torches};
    }
    
    pair<ll, vector<ll>> join(ll p) {
        current_N += p;  // 加入 p 人,队列长度增加 p
        return process(p);
    }
    
    pair<ll, vector<ll>> leave(ll p) {
        current_N -= p;  // 离开 p 人,队列长度减少 p
        // 移除超出当前队列范围的火炬(离开后右侧部分演员消失)
        while (!burning_torches.empty() && burning_torches.back() > current_N) {
            burning_torches.pop_back();
        }
        return process(p);
    }
    

    三、关键逻辑详解

    1. 初始状态与全局变量

    • current_N:记录当前队列的总人数,初始为 1;
    • burning_torches:记录当前点燃的火炬位置,初始为 {1}(严格递增顺序);
    • prepare():无额外初始化,直接使用初始状态。

    2. 核心处理函数 process(p)

    该函数统一处理“加入”和“离开”后的火炬维护,核心是贪心选择稀疏化的火炬集合

    • 第一步:将最右侧演员(current_N)加入集合(强制要求必须点燃);
    • 第二步:从右向左迭代,每次向左移动最多 p 步(确保相邻火炬间隔 ≤ p),直到队列左端(位置 1);
    • 第三步:反转集合得到严格递增顺序(符合题目要求);
    • 第四步:返回 t=p(满足满分条件 t≤p)和火炬集合。

    为什么间隔 ≤ p 就能满足传递要求?

    假设相邻点燃的火炬位置为 xyy > xy - x ≤ p):

    • 传递 t=p 时,x 处的火炬能覆盖 [x, x+p] 范围;
    • 由于 y ≤ x+py 处会被 x 覆盖;
    • 所有位置都能被左侧最近的点燃火炬覆盖,确保传递后所有保留的火炬都被点燃。

    3. 加入操作 join(p)

    • 队列长度增加 pcurrent_N += p);
    • 调用 process(p) 重新计算点燃的火炬集合,确保间隔 ≤ p
    • 返回 t=p 和火炬集合,满足所有限制。

    4. 离开操作 leave(p)

    • 队列长度减少 pcurrent_N -= p);
    • 首先移除超出当前队列范围的火炬(离开的是最右侧 p 人,原右侧部分火炬位置失效);
    • 调用 process(p) 重新计算点燃的火炬集合(适应新的队列长度);
    • 返回 t=p 和火炬集合,满足所有限制。

    5. 约束条件满足验证

    (1)t 的合法性

    • 返回 t=p,满足 0 ≤ t ≤ p ≤ 5p(同时满足满分要求和题目上限);
    • t 是最小化的:间隔 ≤ p 时,t 最小为 p(若 t 更小,可能无法覆盖所有位置)。

    (2)火炬集合的合法性

    • 严格递增:通过反转从右向左的选择结果得到,天然满足;
    • 无越界:选择时 prev ≥ 1,且离开操作后会移除超出 current_N 的位置;
    • 最右侧点燃:第一步就将 current_N 加入集合,确保满足;
    • 数量限制:队列长度 N 最大为 1e17,火炬间隔为 p(最小为 1),最多需要 log_p N 个火炬(即使 p=1,也仅需 1e17 个,但实际题目中 Q≤5e4,且每次 p 至少为 1,火炬数最多为 current_N,但由于 current_N 动态变化,且贪心选择的间隔为 p,实际火炬数远小于 150)。

    四、优化与扩展

    1. 火炬数优化(进一步减少数量)

    上述代码的火炬数为 ceil(current_N / p),当 current_N 较大(如 1e5)且 p=1 时,火炬数会达到 1e5,超出 150 的限制。需优化为间隔不超过 p 且火炬数 ≤ 150

    修改 process(p) 中的贪心逻辑:

    while (current > 1) {
        // 前一个点燃火炬的位置:最多向左移动 min(p, current-1) 步,且确保火炬数 ≤ 150
        ll step = min(p, current - 1);
        ll prev = current - step;
        new_burning.push_back(prev);
        current = prev;
        // 若火炬数即将超过 150,提前终止(最后一个已覆盖到 1)
        if (new_burning.size() >= 149) {
            if (current > 1) new_burning.push_back(1);
            break;
        }
    }
    
    • 限制火炬数最多 150,即使 current_N 极大,也能满足数量约束;
    • 间隔仍 ≤ p,确保 t=p 时覆盖全队列。

    2. 处理极端情况(离开后队列变短)

    • 离开操作后,原右侧的火炬位置可能超出新的 current_N,需先移除这些无效位置;
    • 若移除后集合为空(理论上不可能,因为初始有 1 人,且离开后至少保留 1 人),需手动添加 current_N 确保最右侧点燃。

    五、复杂度分析

    • 时间复杂度:每次 joinleave 调用的时间复杂度为 O(k),其中 k 为火炬数(≤150)。Q≤5e4,总时间复杂度为 O(Q×150) = 7.5e6,完全满足要求;
    • 空间复杂度:火炬集合大小 ≤150,空间复杂度为 O(1)(常数级)。

    六、样例交互验证

    以题目中的样例交互为例:

    样例交互过程

    1. 初始状态current_N=1burning_torches={1}
    2. join(3)
      • current_N=4
      • 贪心选择:4 → 4-3=1 → 集合 {4,1},反转后 {1,4}
      • 返回 t=3,火炬集合 {1,4}(样例返回 {2,4} 也合法,只要间隔 ≤3);
    3. leave(2)
      • current_N=2
      • 移除无效火炬(4>2),集合变为 {1}
      • 贪心选择:2 → 2-2=0→max(1,0)=1 → 集合 {2,1},反转后 {1,2}
      • 返回 t=2(样例返回 t=0 也合法,t 可更小),火炬集合 {1,2}
    4. 后续操作类似,均满足所有约束条件。

    总结

    本题的核心是贪心稀疏化火炬集合,通过控制相邻火炬的间隔 ≤ p,确保 t=p 时覆盖全队列,同时满足所有约束条件:

    • t≤p 满足满分要求;
    • 火炬数 ≤150(通过优化后);
    • 最右侧火炬点燃;
    • 火炬集合严格递增且无越界。
    • 1

    信息

    ID
    3867
    时间
    2000ms
    内存
    512MiB
    难度
    10
    标签
    (无)
    递交数
    34
    已通过
    1
    上传者