1 条题解
-
0
题解:A Light Inconvenience(火炬传递与动态队列维护)
一、题目核心分析
1. 问题本质
维护一个动态变化的演员队列(支持从右侧加入/离开),核心要求如下:
- 初始状态:1 位演员,火炬点燃;
- 每次队列变化后(加入/离开
p人),需指定t(满足0 ≤ t ≤ 5p,且尽可能小),让所有点燃的火炬向右传递t距离(即演员i点燃当且仅当[max(i-t,1), i]中有点燃的火炬); - 最终需保留一组点燃的火炬,满足:
- 最右侧演员的火炬必须点燃;
- 点燃的火炬数 ≤ 150;
- 保留的火炬均需在传递后被点燃。
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就能满足传递要求?假设相邻点燃的火炬位置为
x和y(y > x,y - x ≤ p):- 传递
t=p时,x处的火炬能覆盖[x, x+p]范围; - 由于
y ≤ x+p,y处会被x覆盖; - 所有位置都能被左侧最近的点燃火炬覆盖,确保传递后所有保留的火炬都被点燃。
3. 加入操作
join(p)- 队列长度增加
p(current_N += p); - 调用
process(p)重新计算点燃的火炬集合,确保间隔 ≤p; - 返回
t=p和火炬集合,满足所有限制。
4. 离开操作
leave(p)- 队列长度减少
p(current_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确保最右侧点燃。
五、复杂度分析
- 时间复杂度:每次
join或leave调用的时间复杂度为O(k),其中k为火炬数(≤150)。Q≤5e4,总时间复杂度为O(Q×150) = 7.5e6,完全满足要求; - 空间复杂度:火炬集合大小 ≤150,空间复杂度为
O(1)(常数级)。
六、样例交互验证
以题目中的样例交互为例:
样例交互过程
- 初始状态:
current_N=1,burning_torches={1}; - join(3):
current_N=4;- 贪心选择:4 → 4-3=1 → 集合
{4,1},反转后{1,4}; - 返回
t=3,火炬集合{1,4}(样例返回{2,4}也合法,只要间隔 ≤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};
- 后续操作类似,均满足所有约束条件。
总结
本题的核心是贪心稀疏化火炬集合,通过控制相邻火炬的间隔 ≤
p,确保t=p时覆盖全队列,同时满足所有约束条件:t≤p满足满分要求;- 火炬数 ≤150(通过优化后);
- 最右侧火炬点燃;
- 火炬集合严格递增且无越界。
- 1
信息
- ID
- 3867
- 时间
- 2000ms
- 内存
- 512MiB
- 难度
- 10
- 标签
- (无)
- 递交数
- 34
- 已通过
- 1
- 上传者