[R1E] 过分的子区间


数据规模:1≤k≤n≤2×1051 \le k \le n \le 2 \times 10^5,1≤x,Ai≤1091 \le x,A_i \le 10^9。

思路

「区间内第 kk 小的数 ≥x\ge x」等价于「区间内小于 xx 的数不足 kk 个」。而区间长度小于 kk 时自动满足该条件,所以两类区间可以统一计数。

对每个左端点 ll,区间内小于 xx 的元素个数随右端点 rr 增大而单调不减,因此满足条件(小于 xx 的个数 ≤k−1\le k-1)的最大右端点 rr 也随 ll 增大而单调不减,用 双指针 扫描:

  • 维护指针 rr 和当前区间 [l,r][l,r] 中小于 xx 的个数 cntcnt;
  • 不断右移 rr,直到再加入一个小于 xx 的数就会达到 kk 个为止,此时以 ll 为左端点且满足条件的子区间有 r−l+1r-l+1 个,累加进答案;
  • ll 右移时把 AlA_l 的贡献从 cntcnt 中移除。

复杂度:时间 O(n)O(n),空间 O(n)O(n)。

仓颉实现

查看仓颉实现
import std.env.*
import std.convert.*

main() {
    let reader = getStdIn()
    let nkx = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p => Int64.parse(p) })
    let n = nkx[0]
    let k = nkx[1]
    let x = nkx[2]
    let a = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p => Int64.parse(p) })
    var ans: Int64 = 0
    var r: Int64 = -1
    var less: Int64 = 0
    for (l in 0..n) {
        if (r < l - 1) {
            r = l - 1
            less = 0
        }
        while (r + 1 < n && less + (if (a[r + 1] < x) { 1 } else { 0 }) < k) {
            r += 1
            if (a[r] < x) {
                less += 1
            }
        }
        ans += r - l + 1
        if (l <= r && a[l] < x) {
            less -= 1
        }
    }
    println(ans)
}

要点:

  • 条件写成「小于 xx 的个数 <k< k」,处理时用 Int64,答案最大为全部 n(n+1)/2≈2×1010n(n+1)/2 \approx 2 \times 10^{10} 个子区间,不能存进 Int32。
  • 当 r<l−1r < l-1 时区间为空,需把 rr 拉回 l−1l-1 并把 cntcnt 清零,否则负数会污染计数。