数据规模:1≤t,m,∑m≤2×105,m≤n<998244353。
思路
把每对对应括号看作一个节点,它内部从左到右的若干对括号就是这个节点的儿子;最外层从左到右的若干对括号构成一片有序森林。
可爱的条件恰好说明:
- 1 级节点没有儿子;
- i>1 级节点的所有后代级别都小于 i,并且至少存在一个 i−1 级后代。
因此,一个节点的级别就是以它为根的子树高度。
令 Ti 表示高度恰好为 i 的一棵树的多元生成函数,令 Fi 表示每棵树高度都不超过 i 的有序森林的多元生成函数,其中 xi 标记高度为 i 的节点数。记 F0=1,则
T1=x1,
而高度为 i 的根内部必须是一片最大树高恰好为 i−1 的森林,所以对 i≥2 有
Ti=xi(Fi−1−Fi−2)。
一片有序森林是任意棵树的序列,因此
Fi=1−∑j=1iTj1。
直接展开这些生成函数并不方便。定义
yi=TiFi−1。
由森林的生成函数可得
Fi=1−yiFi−1,Fm=i=1∏m(1−yi)−1。
又因为
Fi−1−Fi−2=yi−1Fi−1,
所以
yi=xiyi−1Fi−12。
令
qi=x1x2⋯xi。
从 y1=x1 开始归纳,可以得到
yi=qij=1∏i−1(1−yj)−2(i−j)。
题目要求高度 i 的节点数为 ai=n−i+1。一个 qi 会给 x1,x2,…,xi 的次数各增加 1,所以令 am+1=0 后,qi 的目标次数为
ai−ai+1={1,n−m+1,i<m,i=m。
记 r=n−m+1,需要求的就是
[q1q2⋯qm−1qmr]Fm。
按 qm,qm−1,…,q1 的顺序依次取系数。取出 qmr 后,(1−yk) 的负指数会增加 2r(m−k)。之后每取出一次 ql,对于所有 k<l,负指数还会增加 2(l−k)。
因此,在准备取 qk 的一次项时,记 d=m−k,此时 (1−yk) 的负指数为
Ek=1+2r(m−k)+2l=k+1∑m−1(l−k)=1+2rd+d(d−1)=1+d(2r+d−1)。
而
[z1](1−z)−Ek=Ek,
所以每一步恰好给答案乘上 Ek。最终答案为
d=1∏m−1(1+d(2(n−m+1)+d−1))。
逐项取模计算即可。
复杂度
每组测试的时间复杂度为 O(m),所有测试合计为 O(∑m);额外空间复杂度为 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))
}
}