[R77F] 巡检
数据规模:, 是 的排列。
思路
先明确要统计的对象:对区间 ,设 在该区间内最小值的位置为 、最大值的位置为 ,要求 。由于 是排列,区间内最值的位置唯一。
直接枚举所有区间并顺便维护最值位置是 ,只能通过 的子任务。改用分治:对区间 取中点 ,先递归统计完全落在 与 内的区间,再统计跨中点区间,即 的区间。长度为 的区间总是合法(),作为递归边界直接返回 。
跨中点的区间被中点切成左段 与右段 。预处理左段后缀最值:对每个 记
以及取到最值的位置 、;右段前缀同理,对每个 记 、、、。这两组量都能从 与 出发向外递推, 求出。
整个区间的最值必然来自左段或右段,按来源分成四种互不重叠的情况。
情况一:最值都在左段
此时整个区间的最小值就是 ,位置 ;最大值就是 ,位置 。两个位置都只与 有关,对称条件 直接解出
也就是说每个 至多产生一个候选 。只需再校验两件事:
- ,保证区间确实跨越中点;
- 加入右段后最值没有被右段抢走,即 且 。
枚举所有左端点即可完整统计本情况。
情况二:最值都在右段
与情况一完全对称:枚举右端点 ,取 、,则候选左端点唯一,为
校验 、、 即可。
情况三:最小值在左段,最大值在右段
此时整个区间的最值分别取自两段,要求
而对称条件写作 ,即
固定 观察 的合法范围。当 增大时, 不增,所以 是一段前缀 ; 不减,所以 是一段后缀 。两者之交仍是连续的一段 (可能为空)。
于是本情况的贡献为: 中满足 的个数。
当 从 向左移动时, 不增、 不减,两个条件都越来越容易满足,因此 与 都只会向右移动。用双指针维护窗口 : 右移时把 放进计数桶, 右移时从桶中扣除,当前 的贡献就是 。
情况四:最大值在左段,最小值在右段
同样是对称的:要求
对称条件写成 ,即
仍限制出一段前缀, 限制出一段后缀,合法范围还是 ; 向左移动时两个界同样单调右移。把桶中记录的值换成 ,答案累加 即可。
实现细节
四个量 、、、 都落在 内,用一个长为 的数组当计数桶即可。每统计完一种情况,把窗口里的元素逐个从桶中撤销,保证进入下一层递归时桶是空的。
复杂度
每层的预处理与四种情况的统计都是 :双指针在每个递归层内单调移动,均摊线性。递归共 层,故时间复杂度 ;递归深度 ,辅助数组与计数桶共 空间。
答案最大为 ,需要用 Int64 存放。
仓颉实现
查看仓颉实现
import std.env.*
import std.convert.*
var n: Int64 = 0
var p = Array<Int64>(1, { _ => 0 })
var mnL = Array<Int64>(1, { _ => 0 })
var mxL = Array<Int64>(1, { _ => 0 })
var pmnL = Array<Int64>(1, { _ => 0 })
var pmxL = Array<Int64>(1, { _ => 0 })
var mnR = Array<Int64>(1, { _ => 0 })
var mxR = Array<Int64>(1, { _ => 0 })
var pmnR = Array<Int64>(1, { _ => 0 })
var pmxR = Array<Int64>(1, { _ => 0 })
var cnt = Array<Int64>(1, { _ => 0 })
func count(L: Int64, R: Int64): Int64 {
if (L > R) {
return 0
}
if (L == R) {
return 1
}
let M = (L + R) / 2
var ans = count(L, M) + count(M + 1, R)
var mn = p[M]
var mx = p[M]
var pmn = M
var pmx = M
var i = M
while (i >= L) {
let v = p[i]
if (v < mn) {
mn = v
pmn = i
}
if (v > mx) {
mx = v
pmx = i
}
mnL[i] = mn
mxL[i] = mx
pmnL[i] = pmn
pmxL[i] = pmx
i -= 1
}
mn = p[M + 1]
mx = p[M + 1]
pmn = M + 1
pmx = M + 1
var j = M + 1
while (j <= R) {
let v = p[j]
if (v < mn) {
mn = v
pmn = j
}
if (v > mx) {
mx = v
pmx = j
}
mnR[j] = mn
mxR[j] = mx
pmnR[j] = pmn
pmxR[j] = pmx
j += 1
}
var l = M
while (l >= L) {
let r = pmnL[l] + pmxL[l] - l
if (r > M && r <= R) {
if (mnR[r] > mnL[l] && mxR[r] < mxL[l]) {
ans += 1
}
}
l -= 1
}
var r = M + 1
while (r <= R) {
let w = pmnR[r] + pmxR[r] - r
if (w >= L && w <= M) {
if (mnL[w] > mnR[r] && mxL[w] < mxR[r]) {
ans += 1
}
}
r += 1
}
var lo = M + 1
var hi = M
l = M
while (l >= L) {
while (hi < R && mnR[hi + 1] > mnL[l]) {
hi += 1
cnt[hi - pmxR[hi]] += 1
}
while (lo <= hi && mxR[lo] <= mxL[l]) {
cnt[lo - pmxR[lo]] -= 1
lo += 1
}
ans += cnt[pmnL[l] - l]
l -= 1
}
while (lo <= hi) {
cnt[lo - pmxR[lo]] -= 1
lo += 1
}
lo = M + 1
hi = M
l = M
while (l >= L) {
while (hi < R && mxR[hi + 1] < mxL[l]) {
hi += 1
cnt[hi - pmnR[hi]] += 1
}
while (lo <= hi && mnR[lo] >= mnL[l]) {
cnt[lo - pmnR[lo]] -= 1
lo += 1
}
ans += cnt[pmxL[l] - l]
l -= 1
}
while (lo <= hi) {
cnt[lo - pmnR[lo]] -= 1
lo += 1
}
return ans
}
main() {
let reader = getStdIn()
n = Int64.parse(reader.readln().getOrThrow())
p = Array<Int64>(n + 1, { _ => 0 })
mnL = Array<Int64>(n + 1, { _ => 0 })
mxL = Array<Int64>(n + 1, { _ => 0 })
pmnL = Array<Int64>(n + 1, { _ => 0 })
pmxL = Array<Int64>(n + 1, { _ => 0 })
mnR = Array<Int64>(n + 1, { _ => 0 })
mxR = Array<Int64>(n + 1, { _ => 0 })
pmnR = Array<Int64>(n + 1, { _ => 0 })
pmxR = Array<Int64>(n + 1, { _ => 0 })
cnt = Array<Int64>(n + 1, { _ => 0 })
let a = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ s: String => Int64.parse(s) })
var i = 0
while (i < n) {
p[i + 1] = a[i]
i += 1
}
println(count(1, n))
}