[R59E] 01序列

  • 难度 中等
  • 时限 1s
  • 空限 512m
  • 前缀和组合计数

数据规模:4n2×1054 \le n \le 2 \times 10^5

思路

一个 0110 子序列由四个位置 i<j<k<li < j < k < l 构成,满足 Si=0,Sj=Sk=1,Sl=0S_i = 0, S_j = S_k = 1, S_l = 0。某个连续子串 [L,R][L, R] 包含这四元组,当且仅当 LiL \le iRlR \ge lLLii 种选法(1i1 \ldots i),RRnl+1n - l + 1 种选法(lnl \ldots n)。因此答案等于所有合法四元组的 i(nl+1)i \cdot (n - l + 1) 之和。

从左到右扫描 SS(位置从 11 起),维护三个量:

  • zsumzsum:已扫描的 00 的位置之和,即 i<p,Si=0i\sum_{i < p, S_i = 0} i
  • jsumjsum:所有满足 i<ji < jSi=0S_i = 0Sj=1S_j = 1 的二元组的 ii 之和;
  • abab:所有满足 i<j<ki < j < kSi=0S_i = 0Sj=Sk=1S_j = S_k = 1 的三元组的 ii 之和,其中 kk 取已扫描到的位置。

扫描到位置 pp 时:

  • Sp=0S_p = 0:以 pp 作为四元组的 ll,贡献为 ab(np+1)ab \cdot (n - p + 1),累加进答案;随后 zsum+=pzsum \mathrel{+}= p
  • Sp=1S_p = 1:先执行 ab+=jsumab \mathrel{+}= jsum(把 pp 当作 kkjj 只能取 pp 之前的 11),再执行 jsum+=zsumjsum \mathrel{+}= zsum(把 pp 当作 jjiipp 之前的 00)。顺序不能颠倒。

jsumjsumabab 始终对 998244353998244353 取模即可。

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

仓颉实现

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

main(): Int64 {
    let reader = getStdIn()
    let n = Int64.parse(reader.readln().getOrThrow())
    let s = reader.readln().getOrThrow()
    let arr = s.toRuneArray()
    let MOD: Int64 = 998244353
    // zsum: 已扫描的 0 的位置(1 起下标)之和
    // jsum: 所有 (0 的位置 i, 1 的位置 j),i < j 的 i 之和
    // ab: 所有满足 i < j < k 且 S[i]=0、S[j]=S[k]=1 的三元组的 i 之和
    var zsum: Int64 = 0
    var jsum: Int64 = 0
    var ab: Int64 = 0
    var ans: Int64 = 0
    for (p in 0..n) {
        if (arr[p] == r'0') {
            ans = (ans + ab * (n - p)) % MOD
            zsum += p + 1
        } else {
            ab = (ab + jsum) % MOD
            jsum = (jsum + zsum) % MOD
        }
    }
    println(ans)
    return 0
}