1 条题解
-
0
题目理解
我们有一条长度为 的沙滩,有 个固定位置的毯子(整数位置,包括 和 )。
接着会来 个新游客(Bajtazar 是最后一个下车),每个新游客会选择距离最近毯子最远的位置,如果有多个这样的位置,选最靠左的。
新游客的位置可以是实数。
给定 个询问,每个询问给出 ,问 Bajtazar 最终铺毯子的位置(输出不可约分数形式)。核心思路
这是一个模拟插点的问题,但 可以很大(),不能直接模拟。
观察:
初始时, 个固定毯子把沙滩分成 个区间。
每个区间 的长度为 。
在区间中间插入新毯子时,会把区间分成两半,每半的长度是 。关键:
- 每次选择插入的区间是当前所有区间中长度最大的,长度相同则选最靠左的。
- 插入后,该区间长度减半,变成两个新区间(长度相等)。
数据结构与算法
我们可以把每个区间表示成一个结构:
- 长度 (实际存储可能放大 来避免浮点误差)
- 起点
- 该区间当前被重复的次数 (因为可能有多个相同长度、起点的区间)
每次我们取长度最大的区间(长度相同按起点排序),然后:
- 处理掉这个区间的前 次插入(对应 个新游客)
- 把区间长度减半,重复次数翻倍(因为每个区间一分为二,数量翻倍)
代码解析
1. 输入与初始化
cin >> n >> m >> q; F(i, 1, n) cin >> a[i], a[i] <<= 30;这里把坐标左移 30 位,相当于乘以 ,这样可以用整数运算避免浮点误差。
2. 初始区间
F(i, 1, n - 1) s[i] = {a[i + 1] - a[i] >> 1, a[i], 1};区间长度取
(a[i+1] - a[i]) >> 1,因为新游客会插在区间中点,所以可用长度是区间长度的一半(到两边的距离)。3. 排序
sort(s + 1, s + n);自定义比较:先按长度 降序,长度相等按起点 升序。
4. 处理询问
dF(i, 60, 0) { F(j, 1, n - 1) { auto &[d, a, t] = s[j]; if (d < 1ll << i) continue; ... } }这里从高位到低位(60 到 0)处理区间长度。
如果区间长度 ,则处理这个区间。5. 计算插入位置
while (now <= q) { auto [x, id] = qu[now]; if (x <= tot + t) ans[id] = a + Cal(x - tot, t) * d, ++now; else break; }tot是已经处理过的游客总数。
Cal(x - tot, t)计算在这个区间内的第几个位置插入。6.
Cal函数int Cal(int x, int t) { if (t == 1) return 1; if (x <= t >> 1) return Cal(x, t >> 1); else return t + Cal(x - (t >> 1), t >> 1); }这是一个递归函数,用于确定在 个相同区间中,第 个插入的位置编号。
它实际上是在一个完全二叉树的叶节点中找第 个位置,保持左右平衡。7. 输出
LL a = ans[i], b = 1ll << 30, g = Gcd(a, b); cout << a / g << "/" << b / g << "\n";因为坐标放大了 ,所以分母是 ,要约分。
算法标签
- 贪心(Greedy)
- 模拟
- 分数处理
- 排序
- 递归/分治
- 整数运算放大法(避免浮点误差)
复杂度分析
- 排序初始区间:
- 处理区间时,每个区间最多被处理 次
- 总复杂度约
总结
这道题的关键在于:
- 把问题转化为在若干区间中反复取最大长度区间插入
- 用整数放大避免浮点误差
- 用
Cal函数快速计算在多个相同区间中的插入顺序 - 按长度分层处理,保证高效性
这样就能在 很大时依然快速求出 Bajtazar 的位置。
- 1
信息
- ID
- 3727
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 4
- 已通过
- 1
- 上传者