[R1E] 过分的子区间

  • 难度 普及/提高-
  • 时限 1s
  • 空限 512m
  • 双指针二分

数据规模:1kn2×1051 \le k \le n \le 2 \times 10^51x,Ai1091 \le x,A_i \le 10^9

思路

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

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

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

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

仓颉实现

import std.env.*
import std.convert.*

main(): Int64 {
    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)
    return 0
}

要点:

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