[R18D]联想
对于 的数据,,。
思路
题目要求按顺序完成 道题。对于第 道题,可以花 直接做,也可以从某道已完成的前置题 联想:若 ,则花费 。
因为按顺序做题,处理到第 道题时所有 均已完成,所以联想的来源不受「如何完成」限制,只受下标限制。于是对固定的 与联想能力 :
- 若存在 使 ,则联想代价为这些 的最小值;
- 否则只能直接做,代价为 。
记 为第 道题到所有前置题的最小代价差( 时无前置题,只能直接做)。注意联想不一定更优:当 时直接做反而更省。因此第 道题的贡献为
第 道题恒为直接做,取 即可让上式自然成立。
计算
朴素做法是边插入边维护一个有序集合,每次查询 的前驱后继。但本题数据范围 ,需要 。
采用经典的 排序 + 双向链表倒序删除 技巧:
- 把所有 按值升序排序,得到值序位置 ;在值序上构建双向链表(
prev/next数组)。 - 按原下标 从大到小遍历。处理 时,链表中尚未被删除的元素恰好是 ,其中 在链表里的前驱和后继(不含自身)就落在 中,且正是值域上离 最近的两个元素,所以 即为二者与 之差的较小者。
- 查询完后把 从链表中删除( 改指针),继续处理 。
离线处理询问
令 。对所有 按 升序排序,维护 与 的前缀和。记 。
对每个询问 ,二分找出 的元素集合(前 个),则
复杂度
- 时间:排序 ,链表删除 ,每个询问二分 ,共 。
- 空间:。
仓颉实现
import std.convert.*
import std.env.*
import std.sort.*
main(): Int64 {
let reader = getStdIn()
let firstLine = reader.readln().getOrThrow().split(" ", removeEmpty: true)
let n = Int64.parse(firstLine[0])
let q = Int64.parse(firstLine[1])
let a = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ s: String => Int64.parse(s) })
// 把原下标按 a 值升序排序,构建值序双向链表
let order = Array<Int64>(n, { k: Int64 => k })
sort(order, key: { k: Int64 => a[k] })
let vs = Array<Int64>(n, { k: Int64 => a[order[k]] })
let pos = Array<Int64>(n, { _ => 0 })
for (k in 0..n) {
pos[order[k]] = k
}
let prev = Array<Int64>(n, { _ => 0 })
let next = Array<Int64>(n, { _ => 0 })
for (k in 0..n) {
prev[k] = k - 1
next[k] = k + 1
}
next[n - 1] = -1
prev[0] = -1
// mind[i] = min_{j<i} |a_j - a_i|;mind[0] = a[0] 表示恒直接做
let mind = Array<Int64>(n, { _ => 0 })
var ii = n - 1
while (ii >= 0) {
let p = pos[ii]
let ai = a[ii]
if (ii == 0) {
mind[ii] = ai
} else {
// 当前存活集合 = {a_0..a_i},前驱后继必在 {a_0..a_{i-1}} 中
var best = Int64(2000000005)
let pr = prev[p]
if (pr >= 0) {
let d = ai - vs[pr]
if (d < best) {
best = d
}
}
let nx = next[p]
if (nx >= 0) {
let d = vs[nx] - ai
if (d < best) {
best = d
}
}
mind[ii] = best
}
// 从链表删除位置 p
let pr = prev[p]
let nx = next[p]
if (pr >= 0) {
next[pr] = nx
}
if (nx >= 0) {
prev[nx] = pr
}
ii -= 1
}
// 离线回答询问:save[i] = min(a[i], mind[i])
var SA = Int64(0)
for (k in 0..n) {
SA += a[k]
}
let save = Array<Int64>(n, { k: Int64 => if (a[k] < mind[k]) { a[k] } else { mind[k] } })
let idx = Array<Int64>(n, { k: Int64 => k })
sort(idx, key: { k: Int64 => mind[k] })
let sm = Array<Int64>(n, { _ => 0 })
let psave = Array<Int64>(n, { _ => 0 })
let pa = Array<Int64>(n, { _ => 0 })
for (k in 0..n) {
let oi = idx[k]
sm[k] = mind[oi]
psave[k] = save[oi]
pa[k] = a[oi]
}
let pSave = Array<Int64>(n + 1, { _ => 0 })
let pA = Array<Int64>(n + 1, { _ => 0 })
for (k in 0..n) {
pSave[k + 1] = pSave[k] + psave[k]
pA[k + 1] = pA[k] + pa[k]
}
var out = StringBuilder()
var qi = Int64(0)
while (qi < q) {
let x = Int64.parse(reader.readln().getOrThrow())
// 二分找 mind <= x 的元素个数(上界)
var lo = Int64(0)
var hi = n
while (lo < hi) {
let mid = (lo + hi) >> 1
if (sm[mid] <= x) {
lo = mid + 1
} else {
hi = mid
}
}
let cnt = lo
let ans = pSave[cnt] + (SA - pA[cnt])
out.append(ans)
out.append('\n')
qi += 1
}
print(out.toString())
return 0
}