[R68F] 求和
- 难度 提高+/省选-
- 时限 1s
- 空限 512m
- 组合数学容斥原理
数据规模:,,。数值相同的元素按不同下标视为不同。
思路
设 为 的幂和, 表示 的下标集合。
第一步:固定子序列位置与 。 对固定的位置集合 和固定的 ,当 遍历 个下标排列时, 恰好以 的重数遍历所有互异下标的有序 元组,故
对 个 求和得到因子 ,于是
第二步:对「互异」做集合划分容斥。 把 写成 下标双射 (即 ,共 种),用划分格上的 Möbius 容斥拆掉「互异」约束:
直观地看:划分 把「同一块内的下标必须相等」计入,块 共用一个值 ,贡献 。
第三步:按块大小形状合并。 对划分 ,记其块大小形状为 (即 的一个整数拆分),则 只依赖形状。固定 后, 遍历 个双射,按每个块实际分到的 下标集合分组:
其中 为对所有有序划分 ( 两两不交、、)求和 。形状为 的集合划分个数为 ,其中 是形状中等于 的块数。把以上各项与 相乘, 全部相消,得到最终公式:
第四步:计算。 , 的整数拆分只有 种。幂和 对每个 做一次子集 DP 求出:,( 取 的最低二进制位),累加进 。 用状压 DP:设 为前若干块恰好占用下标集合 的贡献和,初始 ;对大小为 的块做转移
最后取 。分母 用费马小定理求逆元,答案最后乘 。
复杂度
时间 ( 为整数拆分数),空间 。 时 ,主体 次模乘。
仓颉实现
import std.collection.*
import std.convert.*
import std.env.*
const MOD: Int64 = 998244353
func powmod(base: Int64, e: Int64): Int64 {
var r: Int64 = 1
var b = base % MOD
var ex = e
while (ex > 0) {
if (ex % 2 == 1) {
r = r * b % MOD
}
b = b * b % MOD
ex = ex / 2
}
return r
}
// 枚举 m 的整数拆分(非增序列),存到 shapes
func genShapes(rem: Int64, mn: Int64, cur: ArrayList<Int64>, shapes: ArrayList<Array<Int64>>): Unit {
if (rem == 0) {
let arr = Array<Int64>(cur.size, { _ => 0 })
for (k in 0..cur.size) {
arr[k] = cur[k]
}
shapes.add(arr)
return
}
var x = mn
if (x > rem) {
x = rem
}
while (x >= 1) {
cur.add(x)
genShapes(rem - x, x, cur, shapes)
cur.remove(at: cur.size - 1)
x -= 1
}
}
main(): Int64 {
let reader = getStdIn()
let nm = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
let n = nm[0]
let m = nm[1]
let a = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
let b = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
let mm = m
let sz = 1 << mm
// fact[k] = k!
let fact = Array<Int64>(mm + 1, { _ => 1 })
for (k in 1..mm + 1) {
fact[k] = fact[k - 1] * k % MOD
}
// lsbIdx[mask]:mask 最低位 1 的下标(0 起)
let lsbIdx = Array<Int64>(sz, { _ => 0 })
for (mask in 1..sz) {
if (mask % 2 == 1) {
lsbIdx[mask] = 0
} else {
lsbIdx[mask] = lsbIdx[mask / 2] + 1
}
}
// pop[mask]:1 的个数;masksByPop[t]:含 t 个 1 的所有 mask
let pop = Array<Int64>(sz, { _ => 0 })
let masksByPop = Array<ArrayList<Int64>>(mm + 1, { _ => ArrayList<Int64>() })
for (mask in 0..sz) {
if (mask > 0) {
pop[mask] = pop[mask / 2] + mask % 2
}
masksByPop[pop[mask]].add(mask)
}
// P[mask] = sum_i a_i^sum(b 中 mask 对应下标的 b 值),模 MOD
let P = Array<Int64>(sz, { _ => 0 })
P[0] = n % MOD
let row = Array<Int64>(sz, { _ => 0 })
let w = Array<Int64>(mm, { _ => 0 })
for (i in 0..n) {
row[0] = 1
for (j in 0..mm) {
w[j] = powmod(a[i], b[j])
}
for (mask in 1..sz) {
let j = lsbIdx[mask]
row[mask] = row[mask ^ (1 << j)] * w[j] % MOD
}
for (mask in 1..sz) {
P[mask] = (P[mask] + row[mask]) % MOD
}
}
// 枚举 m 的整数拆分(形状),对每个形状算 T 并累加
let shapes = ArrayList<Array<Int64>>()
genShapes(mm, mm, ArrayList<Int64>(), shapes)
let fullMask = sz - 1
var ans: Int64 = 0
for (shape in shapes) {
// 系数:prod_i (-1)^{t_i-1} (t_i-1)! / prod_s (c_s)!
var num: Int64 = 1
let cnt = Array<Int64>(mm + 1, { _ => 0 })
for (t in shape) {
cnt[t] += 1
var sgn: Int64 = 1
if (t % 2 == 0) {
sgn = MOD - 1
}
num = num * sgn % MOD * fact[t - 1] % MOD
}
var den: Int64 = 1
for (s in 1..mm + 1) {
if (cnt[s] > 0) {
den = den * fact[cnt[s]] % MOD
}
}
let coeff = num * powmod(den, MOD - 2) % MOD
// T:按拆分顺序把 b 的下标集合划分到各块(块有标号),DP 枚举
var f = Array<Int64>(sz, { _ => 0 })
f[0] = 1
var used: Int64 = 0
for (t in shape) {
let g = Array<Int64>(sz, { _ => 0 })
for (U in masksByPop[used]) {
if (f[U] == 0) {
continue
}
for (S in masksByPop[t]) {
if ((S & U) != 0) {
continue
}
g[U | S] = (g[U | S] + f[U] * P[S]) % MOD
}
}
f = g
used += t
}
ans = (ans + coeff * f[fullMask]) % MOD
}
// 乘 n!
for (k in 2..n + 1) {
ans = ans * k % MOD
}
println(ans)
return 0
}