[R77F] 巡检


数据规模:1≤n≤2×1051 \le n \le 2 \times 10^5,pp 是 1,2,…,n1,2,\dots,n 的排列。

思路

先明确要统计的对象:对区间 [l,r][l,r],设 pp 在该区间内最小值的位置为 uu、最大值的位置为 vv,要求 u+v=l+ru+v=l+r。由于 pp 是排列,区间内最值的位置唯一。

直接枚举所有区间并顺便维护最值位置是 O(n2)O(n^2),只能通过 n≤3000n\le 3000 的子任务。改用分治:对区间 [L,R][L,R] 取中点 M=⌊(L+R)/2⌋M=\lfloor (L+R)/2\rfloor,先递归统计完全落在 [L,M][L,M] 与 [M+1,R][M+1,R] 内的区间,再统计跨中点区间,即 l≤M<rl\le M<r 的区间。长度为 11 的区间总是合法(u=v=l=ru=v=l=r),作为递归边界直接返回 11。

跨中点的区间被中点切成左段 [l,M][l,M] 与右段 [M+1,r][M+1,r]。预处理左段后缀最值:对每个 l≤Ml\le M 记

mnL(l)=min⁡l≤i≤Mpi,mxL(l)=max⁡l≤i≤Mpi,mn_L(l)=\min_{l\le i\le M}p_i,\qquad mx_L(l)=\max_{l\le i\le M}p_i,

以及取到最值的位置 posmnL(l)posmn_L(l)、posmxL(l)posmx_L(l);右段前缀同理,对每个 r>Mr>M 记 mnR(r)mn_R(r)、mxR(r)mx_R(r)、posmnR(r)posmn_R(r)、posmxR(r)posmx_R(r)。这两组量都能从 MM 与 M+1M+1 出发向外递推,O(R−L+1)O(R-L+1) 求出。

整个区间的最值必然来自左段或右段,按来源分成四种互不重叠的情况。

情况一:最值都在左段

此时整个区间的最小值就是 mnL(l)mn_L(l),位置 u=posmnL(l)u=posmn_L(l);最大值就是 mxL(l)mx_L(l),位置 v=posmxL(l)v=posmx_L(l)。两个位置都只与 ll 有关,对称条件 u+v=l+ru+v=l+r 直接解出

r=posmnL(l)+posmxL(l)−l,r=posmn_L(l)+posmx_L(l)-l,

也就是说每个 ll 至多产生一个候选 rr。只需再校验两件事:

  • M<r≤RM<r\le R,保证区间确实跨越中点;
  • 加入右段后最值没有被右段抢走,即 mnR(r)>mnL(l)mn_R(r)>mn_L(l) 且 mxR(r)<mxL(l)mx_R(r)<mx_L(l)。

枚举所有左端点即可完整统计本情况。

情况二:最值都在右段

与情况一完全对称:枚举右端点 rr,取 u=posmnR(r)u=posmn_R(r)、v=posmxR(r)v=posmx_R(r),则候选左端点唯一,为

l=posmnR(r)+posmxR(r)−r.l=posmn_R(r)+posmx_R(r)-r.

校验 L≤l≤ML\le l\le M、mnL(l)>mnR(r)mn_L(l)>mn_R(r)、mxL(l)<mxR(r)mx_L(l)<mx_R(r) 即可。

情况三:最小值在左段,最大值在右段

此时整个区间的最值分别取自两段,要求

mnL(l)<mnR(r),mxL(l)<mxR(r).mn_L(l)<mn_R(r),\qquad mx_L(l)<mx_R(r).

而对称条件写作 posmnL(l)+posmxR(r)=l+rposmn_L(l)+posmx_R(r)=l+r,即

posmnL(l)−l=r−posmxR(r).posmn_L(l)-l=r-posmx_R(r).

固定 ll 观察 rr 的合法范围。当 rr 增大时,mnR(r)mn_R(r) 不增,所以 mnR(r)>mnL(l)mn_R(r)>mn_L(l) 是一段前缀 r≤hir\le hi;mxR(r)mx_R(r) 不减,所以 mxR(r)>mxL(l)mx_R(r)>mx_L(l) 是一段后缀 r≥lor\ge lo。两者之交仍是连续的一段 [lo(l),hi(l)][lo(l),hi(l)](可能为空)。

于是本情况的贡献为:r∈[lo(l),hi(l)]r\in[lo(l),hi(l)] 中满足 r−posmxR(r)=posmnL(l)−lr-posmx_R(r)=posmn_L(l)-l 的个数。

当 ll 从 MM 向左移动时,mnL(l)mn_L(l) 不增、mxL(l)mx_L(l) 不减,两个条件都越来越容易满足,因此 lo(l)lo(l) 与 hi(l)hi(l) 都只会向右移动。用双指针维护窗口 [lo,hi][lo,hi]:hihi 右移时把 r−posmxR(r)r-posmx_R(r) 放进计数桶,lolo 右移时从桶中扣除,当前 ll 的贡献就是 cnt[posmnL(l)−l]cnt[posmn_L(l)-l]。

情况四:最大值在左段,最小值在右段

同样是对称的:要求

mxL(l)<mxR(r),mnL(l)>mnR(r),mx_L(l)<mx_R(r),\qquad mn_L(l)>mn_R(r),

对称条件写成 posmxL(l)+posmnR(r)=l+rposmx_L(l)+posmn_R(r)=l+r,即

posmxL(l)−l=r−posmnR(r).posmx_L(l)-l=r-posmn_R(r).

mxR(r)<mxL(l)mx_R(r)<mx_L(l) 仍限制出一段前缀,mnR(r)<mnL(l)mn_R(r)<mn_L(l) 限制出一段后缀,合法范围还是 [lo(l),hi(l)][lo(l),hi(l)];ll 向左移动时两个界同样单调右移。把桶中记录的值换成 r−posmnR(r)r-posmn_R(r),答案累加 cnt[posmxL(l)−l]cnt[posmx_L(l)-l] 即可。

实现细节

四个量 posmnL(l)−lposmn_L(l)-l、posmxL(l)−lposmx_L(l)-l、r−posmxR(r)r-posmx_R(r)、r−posmnR(r)r-posmn_R(r) 都落在 [0,n−1][0,n-1] 内,用一个长为 n+1n+1 的数组当计数桶即可。每统计完一种情况,把窗口里的元素逐个从桶中撤销,保证进入下一层递归时桶是空的。

复杂度

每层的预处理与四种情况的统计都是 O(len)O(len):双指针在每个递归层内单调移动,均摊线性。递归共 O(log⁡n)O(\log n) 层,故时间复杂度 O(nlog⁡n)O(n\log n);递归深度 O(log⁡n)O(\log n),辅助数组与计数桶共 O(n)O(n) 空间。

答案最大为 n(n+1)/2≈2×1010n(n+1)/2\approx 2\times 10^{10},需要用 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))
}