[R36E]出题组2
对于 的数据,,,答案对 取模。
思路
把 个有标号的人分成若干组,每组大小 ,且每组内指定一名出题人( 种选法)。这是典型的带权集合划分计数,用指数生成函数(EGF)处理。
一个大小为 的组,在固定的 个标号元素上有 种指定出题人的方式,因此单组的 EGF 为:
集合划分的 EGF 是 ,于是答案 。
设 。由 逐项比对系数可得:
再代回 ,用下降阶乘消去阶乘,得到 的直接递推(对 ,越界项视为 ):
初值 ,。验证:、、,与样例一致。
实现时读入全部询问,取最大值 一次性递推到 ,再逐条回答;系数里的分数用模逆元表示( 为质数),乘法注意随时取模。
复杂度
- 时间复杂度:, 为询问中最大的 。
- 空间复杂度:。
仓颉实现
查看仓颉实现
import std.convert.*
import std.env.*
const MOD: Int64 = 998244353
// 快速幂,用于求模逆元
func power(a: Int64, e: Int64): Int64 {
var base = a % MOD
var exp = e
var res: Int64 = 1
while (exp > 0) {
if (exp % 2 == 1) {
res = res * base % MOD
}
base = base * base % MOD
exp /= 2
}
return res
}
// 模乘:避免中间结果溢出
func mul(a: Int64, b: Int64): Int64 {
return (a % MOD) * (b % MOD) % MOD
}
func solve(): Unit {
let reader = getStdIn()
let t = Int64.parse(reader.readln().getOrThrow())
let ns = Array<Int64>(t, { _ => 0 })
var maxN: Int64 = 0
for (i in 0..t) {
let x = Int64.parse(reader.readln().getOrThrow())
ns[i] = x
if (x > maxN) {
maxN = x
}
}
// EGF: exp(x^2/1! + x^3/2! + x^4/3! + x^5/4!),
// f[n] = 2(n-1)f[n-2] + (3/2)(n-1)(n-2)f[n-3]
// + (2/3)(n-1)(n-2)(n-3)f[n-4]
// + (5/24)(n-1)(n-2)(n-3)(n-4)f[n-5]
let c2 = mul(3, power(2, MOD - 2))
let c3 = mul(2, power(3, MOD - 2))
let c4 = mul(5, power(24, MOD - 2))
let f = Array<Int64>(maxN + 1, { _ => 0 })
f[0] = 1
// f[1] = 0:一个人无法成组
var n: Int64 = 2
while (n <= maxN) {
let nm1 = n - 1
var s = mul(mul(2, nm1), f[n - 2])
if (n >= 3) {
s = (s + mul(mul(mul(c2, nm1), n - 2), f[n - 3])) % MOD
}
if (n >= 4) {
s = (s + mul(mul(mul(mul(c3, nm1), n - 2), n - 3), f[n - 4])) % MOD
}
if (n >= 5) {
s = (s + mul(mul(mul(mul(mul(c4, nm1), n - 2), n - 3), n - 4), f[n - 5])) % MOD
}
f[n] = s
n += 1
}
for (i in 0..t) {
println(f[ns[i]])
}
}
main() {
solve()
}