[R54F]可爱的多级括号序列


数据规模:1≤t,m,∑m≤2×1051 \le t,m,\sum m \le 2 \times 10^5,m≤n<998244353m \le n < 998244353。

思路

把每对对应括号看作一个节点,它内部从左到右的若干对括号就是这个节点的儿子;最外层从左到右的若干对括号构成一片有序森林。

可爱的条件恰好说明:

  • 11 级节点没有儿子;
  • i>1i>1 级节点的所有后代级别都小于 ii,并且至少存在一个 i−1i-1 级后代。

因此,一个节点的级别就是以它为根的子树高度。

令 TiT_i 表示高度恰好为 ii 的一棵树的多元生成函数,令 FiF_i 表示每棵树高度都不超过 ii 的有序森林的多元生成函数,其中 xix_i 标记高度为 ii 的节点数。记 F0=1F_0=1,则

T1=x1,T_1=x_1,

而高度为 ii 的根内部必须是一片最大树高恰好为 i−1i-1 的森林,所以对 i≥2i\ge 2 有

Ti=xi(Fi−1−Fi−2)。T_i=x_i(F_{i-1}-F_{i-2})。

一片有序森林是任意棵树的序列,因此

Fi=11−∑j=1iTj。F_i=\frac{1}{1-\sum_{j=1}^{i}T_j}。

直接展开这些生成函数并不方便。定义

yi=TiFi−1。y_i=T_iF_{i-1}。

由森林的生成函数可得

Fi=Fi−11−yi,Fm=∏i=1m(1−yi)−1。F_i=\frac{F_{i-1}}{1-y_i}, \qquad F_m=\prod_{i=1}^{m}(1-y_i)^{-1}。

又因为

Fi−1−Fi−2=yi−1Fi−1,F_{i-1}-F_{i-2}=y_{i-1}F_{i-1},

所以

yi=xiyi−1Fi−12。y_i=x_i y_{i-1}F_{i-1}^2。

令

qi=x1x2⋯xi。q_i=x_1x_2\cdots x_i。

从 y1=x1y_1=x_1 开始归纳,可以得到

yi=qi∏j=1i−1(1−yj)−2(i−j)。y_i=q_i\prod_{j=1}^{i-1}(1-y_j)^{-2(i-j)}。

题目要求高度 ii 的节点数为 ai=n−i+1a_i=n-i+1。一个 qiq_i 会给 x1,x2,…,xix_1,x_2,\ldots,x_i 的次数各增加 11,所以令 am+1=0a_{m+1}=0 后,qiq_i 的目标次数为

ai−ai+1={1,i<m,n−m+1,i=m。a_i-a_{i+1}= \begin{cases} 1,&i<m,\\ n-m+1,&i=m。 \end{cases}

记 r=n−m+1r=n-m+1,需要求的就是

[q1q2⋯qm−1qmr]Fm。[q_1q_2\cdots q_{m-1}q_m^r]F_m。

按 qm,qm−1,…,q1q_m,q_{m-1},\ldots,q_1 的顺序依次取系数。取出 qmrq_m^r 后,(1−yk)(1-y_k) 的负指数会增加 2r(m−k)2r(m-k)。之后每取出一次 qlq_l,对于所有 k<lk<l,负指数还会增加 2(l−k)2(l-k)。

因此,在准备取 qkq_k 的一次项时,记 d=m−kd=m-k,此时 (1−yk)(1-y_k) 的负指数为

Ek=1+2r(m−k)+2∑l=k+1m−1(l−k)=1+2rd+d(d−1)=1+d(2r+d−1)。\begin{aligned} E_k &=1+2r(m-k)+2\sum_{l=k+1}^{m-1}(l-k)\\ &=1+2rd+d(d-1)\\ &=1+d(2r+d-1)。 \end{aligned}

而

[z1](1−z)−Ek=Ek,[z^1](1-z)^{-E_k}=E_k,

所以每一步恰好给答案乘上 EkE_k。最终答案为

∏d=1m−1(1+d(2(n−m+1)+d−1))。\boxed{\prod_{d=1}^{m-1}\left(1+d(2(n-m+1)+d-1)\right)}。

逐项取模计算即可。

复杂度

每组测试的时间复杂度为 O(m)O(m),所有测试合计为 O(∑m)O(\sum m);额外空间复杂度为 O(1)O(1)。

仓颉实现

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

let MOD: Int64 = 998244353

func solve(reader: ConsoleReader): Int64 {
    let nm = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
    let n = nm[0]
    let m = nm[1]
    let remaining = n - m + 1

    var answer: Int64 = 1
    var d: Int64 = 1
    while (d < m) {
        let factor = (1 + d * (2 * remaining + d - 1)) % MOD
        answer = answer * factor % MOD
        d += 1
    }
    return answer
}

main() {
    let reader = Console.stdIn
    let firstLine = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
    let t = firstLine[1]
    for (_ in 0..t) {
        println(solve(reader))
    }
}