[R65E] 运动的点
- 难度 普及+/提高
- 时限 2s
- 空限 512m
- 排序二分
数据规模:,,,。
思路
所有点的运动区间长度相同,周期都是 。设 ,:
- 初始向右()的点, 秒后位于 ;
- 初始向左()的点, 秒后位于 。
时就是匀速走 秒; 时已经到达端点并折返,等效于只走了 秒。
把点按 分成两组,各自排序得到数组 、。询问时刻两组的位置分别为 与 ,两个数组都仍然有序,问题转化为:两个有序数组归并后求第 小。
二分从 中取的元素个数 :从 中取前 个,从 中取前 个,其中 。这 个数恰好构成全体元素的前 小,当且仅当:
- 中取出的最大值不超过 中未取的最小值:(当 且 时需检查);
- 中取出的最大值不超过 中未取的最小值:(当 且 时需检查)。
第一个条件随 增大单调变难,第二个条件随 增大单调变易,两者必有交集(第 小一定存在)。因此二分出满足第一个条件的最大 后,第二个条件自动成立,答案为
某一边一个都不取时,只取另一边对应的元素即可。
复杂度
排序 ;每个询问二分 。总时间复杂度 ,空间复杂度 。
仓颉实现
import std.env.*
import std.convert.*
import std.collection.*
import std.sort.*
main() {
let reader = getStdIn()
let line0 = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
let n = line0[0]
let q = line0[1]
let L = line0[2]
var listA = ArrayList<Int64>()
var listB = ArrayList<Int64>()
for (_ in 0..n) {
let xy = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
if (xy[1] == 0) {
listA.add(xy[0])
} else {
listB.add(xy[0])
}
}
let na = listA.size
let nb = listB.size
sort(listA)
sort(listB)
let sb = StringBuilder()
for (_ in 0..q) {
let tk = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
let t = tk[0]
let k = tk[1]
let period = 2 * L
let tp = t % period
let d = if (tp <= L) { tp } else { period - tp }
var lo = k - nb
if (lo < 0) {
lo = 0
}
var hi = k
if (hi > na) {
hi = na
}
// 二分最大的 x,满足条件 1:A 中取出的最大值 <= B 中未取的最小值
while (lo < hi) {
let mid = (lo + hi + 1) / 2
let good = mid == 0 || k - mid >= nb || listA[mid - 1] + d <= listB[k - mid] + L - d
if (good) {
lo = mid
} else {
hi = mid - 1
}
}
// 条件 2(B 中取出的最大值 <= A 中未取的最小值)在数学上必然满足;答案取两边取出的最大值
var ans = if (lo > 0) { listA[lo - 1] + d } else { listB[k - 1] + L - d }
if (k - lo > 0) {
let cand = listB[k - lo - 1] + L - d
if (cand > ans) {
ans = cand
}
}
sb.append(ans)
sb.append('\n')
}
print(sb.toString())
}