Description
考虑 (1,2,...,2N−1) 的一个排列 P=(P1,P2,...,P2N−1)。称 P 像堆当且仅当 Pi<P2i 和 Pi<P2i+1 对 1≤i≤2N−1−1 成立。
给定正整数 A 和 B。令 U=2A,V=2B+1−1。在所有像堆的排列中任取一个,求 PU<PV 的概率。模 998244353。
Solution
容易发现 U 和 V 分别是小根堆里面第 A 行最左边的一个和第 B 行最右边的一个,因此 U 相当于从根一直往左跳的结果,V 相当于从根一直往右跳的结果。
又因为求的是概率,所以我们就只需要关心从根往左和往右跳的路径上的数的大小关系。
考虑从小到大往两条路径上加数,然后 dp。
设 fi,j,0/1 表示当前左边加到了第 i 行,右边加到了第 j 行,且最后一次是加到了左边/右边的概率,初始值是 f0,0,0=1。
由于左右边的概率不定,所以要求出往左边加和往右边的概率。
设 i+1 为根的子树大小为 x,j+1 为根的子树大小为 y。那么这 x+y 个数的最小值是 i+1 的概率就是 (xx+y)(x−1x+y−1)=x+yx,所以下一步加到左边的概率就是 x+yx,加到右边的概率就是 x+yy。
所以:
fi+1,j,0←(fi,j,0+fi,j,1)⋅x+yxfi,j+1,1←(fi,j,0+fi,j,1)⋅x+yy
第 i 行对应点的子树大小很好求,就是 2n−i−1。
最后就是最终答案。
考虑枚举左边第一次到达 A 时右边的位置,假设为 k,那么到达这个状态后,之后怎么加都会满足 PU<PV,所以贡献就是 fA,i,0。
所以结果为:i=1∑B−1fA,i,0。
时间复杂度:O(n2logMOD)。
(那个 logMOD 是在转移过程中求逆元的 log)
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| #include <bits/stdc++.h>
using i64 = int64_t;
const int kMaxN = 5005, kMod = 998244353;
int n, A, B; int f[kMaxN][kMaxN][2], s[kMaxN];
int qpow(int bs, int idx = kMod - 2) { int ret = 1; for (; idx; idx >>= 1, bs = (i64)bs * bs % kMod) if (idx & 1) ret = (i64)ret * bs % kMod; return ret; }
void dickdreamer() { std::cin >> n >> A >> B; s[n - 1] = 1; for (int i = n - 2; ~i; --i) s[i] = (2ll * s[i + 1] + 1) % kMod; f[0][0][0] = 1; for (int i = 0; i <= A; ++i) { for (int j = 0; j <= B; ++j) { int p = (i64)s[i + 1] * qpow((s[i + 1] + s[j + 1]) % kMod) % kMod; f[i + 1][j][0] = (f[i + 1][j][0] + (i64)p * (f[i][j][0] + f[i][j][1]) % kMod) % kMod; f[i][j + 1][1] = (f[i][j + 1][1] + (i64)(kMod + 1 - p) % kMod * (f[i][j][0] + f[i][j][1]) % kMod) % kMod; } } int ans = 0; for (int i = 0; i < B; ++i) ans = (ans + f[A][i][0]) % kMod; std::cout << ans << '\n'; }
int32_t main() { #ifdef ORZXKR freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout); #endif std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0); int T = 1; while (T--) dickdreamer(); return 0; }
|