[R35E]锦标赛
数据规模:,。
思路
赛程固定:选手按编号顺序配对,胜者保持相对顺序进入下一轮。因此整个赛程就是一棵完整的二叉树——第 轮(从 0 开始)中,选手只会与「相邻的 人小组」里的选手相遇,两个小组合并成一个 人小组。
设 为选手 赢得其所在 人小组(即连赢 场)的概率,边界 。
转移时考虑第 轮的一个 人小组 ,拆成左半区 与右半区 :
- 对左半区选手 :先以概率 赢下自己的半区;右半区的胜者是 的概率为 , 战胜 的概率为 。于是
- 右半区选手对称处理。
最终答案就是 (全体选手所在小组即整个锦标赛)。
概率是分数,对模数 (质数)取模时用逆元:。由于 ,可以按线性递推 预处理 的所有逆元,转移时 查询。
复杂度
每对「可能相遇」的选手 恰在其相遇那一轮被枚举一次,共 对。 时约为 次运算。时间 ,空间 。
仓颉实现
查看仓颉实现
import std.env.*
import std.convert.*
const MOD: Int64 = 998244353
const MAXS: Int64 = 2000000
main() {
let reader = getStdIn()
let n = Int64.parse(reader.readln().getOrThrow())
let a = reader.readln().getOrThrow().split(" ", removeEmpty: true).map({ p: String => Int64.parse(p) })
let m = 1 << n
// 预处理 1..2000000 的模逆元(a_i + a_j 最大 2e6)
let inv = Array<Int64>(MAXS + 1, { _ => 0 })
inv[1] = 1
var i: Int64 = 2
while (i <= MAXS) {
inv[i] = MOD - (MOD / i) * inv[MOD % i] % MOD
i = i + 1
}
// prev[x]:选手 x 赢得其所在 2^r 人小组的概率;初始 r=0 时概率为 1
var prev = Array<Int64>(m, { _ => 1 })
var cur = Array<Int64>(m, { _ => 0 })
var r: Int64 = 0
while (r < n) {
let half = 1 << r
let block = half << 1
var b: Int64 = 0
while (b < m) {
// 左半区选手
var p: Int64 = b
while (p < b + half) {
var s: Int64 = 0
var q: Int64 = b + half
while (q < b + block) {
s = (s + prev[q] * a[p] % MOD * inv[a[p] + a[q]]) % MOD
q = q + 1
}
cur[p] = prev[p] * s % MOD
p = p + 1
}
// 右半区选手
var q: Int64 = b + half
while (q < b + block) {
var s: Int64 = 0
var p: Int64 = b
while (p < b + half) {
s = (s + prev[p] * a[q] % MOD * inv[a[p] + a[q]]) % MOD
p = p + 1
}
cur[q] = prev[q] * s % MOD
q = q + 1
}
b = b + block
}
let t = prev
prev = cur
cur = t
r = r + 1
}
var k: Int64 = 0
while (k < m) {
if (k > 0) {
print(" ")
}
print(prev[k])
k = k + 1
}
println()
}