[R8E] 区间MEX
- 难度 提高
- 时限 2s
- 空限 512m
- MEX前缀最值
数据规模:, 是 的排列。
思路
记 为值 在 中的下标。把答案转成「至少」的形式更好算:令 为 的区间数量,则 的区间数 。
等价于 全都落在 内,即 且 。维护前缀最小下标 、前缀最大下标 ,则
等于区间总数 ()。从 起逐步递推 与 即得 。
复杂度:时间 ,空间 。
仓颉实现
import std.convert.*
import std.env.*
main(): Int64 {
let reader = getStdIn()
let n = Int64.parse(reader.readln().getOrThrow())
let p = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p => Int64.parse(p) })
let np1 = n + 1
let pos = Array<Int64>(np1, { _ => 0 })
for (i in 0..np1) {
pos[p[i]] = i + 1
}
let mx = Array<Int64>(np1, { _ => 0 })
let mn = Array<Int64>(np1, { _ => 0 })
mx[0] = pos[0]
mn[0] = pos[0]
for (i in 1..np1) {
mx[i] = if (pos[i] > mx[i - 1]) { pos[i] } else { mx[i - 1] }
mn[i] = if (pos[i] < mn[i - 1]) { pos[i] } else { mn[i - 1] }
}
let L = np1
let total = L * (L + 1) / 2
var prev = total
let out = StringBuilder()
for (i in 0..=n) {
let nxt = mn[i] * (L - mx[i] + 1)
out.append(prev - nxt)
if (i < n) {
out.append(" ")
}
prev = nxt
}
println(out.toString())
return 0
}
要点:
- 「恰好等于」难数时改数「至少」,再用差分()还原,是这类计数题的常用套路。
- 要求 全在区间内,于是只剩前缀最值这一约束,直接 扫描。