题目描述 https://ac.nowcoder.com/acm/contest/11166/I
Solution 首先我们发现可以通过算概率得出期望
我们令 $f[i][j]$ 表示第一个人走到 $i$,第二个人走到 $j$ 的且当前轮到第一个人走
$g[i][j]$ 表示第一个人走到 $i$,第二个人走到 $j$ 的且当前轮到第二个人走
我们发现所求的期望就是 $\sum f[i][j]+g[i][j]$
至于原因,我们可以这样考虑,首先我们记录的 $f[i][j]$ 实际上是第一轮走到 $(i,j)$ 的概率加上第二轮走到 $(i,j)$ 的概率加上第三轮$\cdots$
然后我们考虑一下如果记录轮数的实际转移的样子
我们在叶子计算概率乘上深度等价于在每个点计算一次概率,注意到一个节点的值等于所有儿子节点的值的和
原因在于这个点在向下转移的时候是将自己的权值拆成若干份(概率和相加为 $1$)乘下去的
那么我们现在就得到了一个 $O(n^3)$ 的 $dp$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 f[0 ][0 ] = 1 ; for (int i = 0 ; i <= n; ++i) for (int j = 0 ; j <= n; ++j) { if (f[i][j]) { int cnt = 0 ; for (int k = i + 1 ; k <= n; ++k) if (a[k] > max(a[i], a[j])) ++cnt; for (int k = i + 1 ; k <= n; ++k) if (a[k] > max(a[i], a[j])) g[k][j] = (g[k][j] + f[i][j] * inv[cnt]) % p; } if (g[i][j]) { int cnt = 0 ; for (int k = j + 1 ; k <= n; ++k) if (a[k] > max(a[i], a[j])) ++cnt; for (int k = j + 1 ; k <= n; ++k) if (a[k] > max(a[i], a[j])) f[i][k] = (f[i][k] + g[i][j] * inv[cnt]) % p; } }
然后我们考虑如何优化,我们发现 $f[i][j]$ 在转移的时候,$j$ 是不动的且只会转移到大于 $i$ 的一些 $k$
那么我们考虑做一个前缀和的优化,我们改变 $dp$ 数组的含义
我们令 $f[i][j]$ 表示第一个人要选一个大于 $i$ 的位置,第二个人当前在 $j$,现在轮到第一个人选,$g[i][j]$ 同理
那么当 $a[i+1]>a[j]$ 时,我们可以将 $f[i][j]$ 转移到 $g[i+1][j]$,同时 $f[i][j]$ 也要转移到 $f[i+1][j]$,$g[i][j]$ 的转移同理
不过这时候我们要注意统计答案时要统计能正确转移的答案,$cnt$ 和逆元可以预处理
时间复杂度 $O(n^2)$
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 51 52 53 #include <iostream> #include <vector> #define maxn 5010 #define ll long long #define INF 1000000000 using namespace std ;const int p = 998244353 ;ll pow_mod (ll x, ll n) { ll s = 1 ; for (; n; n >>= 1 , x = x * x % p) if (n & 1 ) s = s * x % p; return s; } ll inv[maxn]; int n, a[maxn], cnt[maxn][maxn];ll f[maxn][maxn], g[maxn][maxn]; int main () { ios::sync_with_stdio(false ); cin .tie(nullptr ); cout .tie(nullptr ); cin >> n; for (int i = 1 ; i <= n; ++i) cin >> a[i]; for (int i = n; i; --i) for (int j = 0 ; j <= n; ++j) cnt[i][j] = cnt[i + 1 ][j] + (a[i] > j); inv[1 ] = 1 ; for (int i = 2 ; i <= n; ++i) inv[i] = -(p / i) * inv[p % i] % p; f[0 ][0 ] = 1 ; ll ans = 0 ; for (int i = 0 ; i <= n; ++i) for (int j = 0 ; j <= n; ++j) { if (f[i][j] && i < n) { if (a[i + 1 ] > a[j]) { g[i + 1 ][j] = (g[i + 1 ][j] + f[i][j] * inv[cnt[i + 1 ][a[j]]]) % p; ans = (ans + f[i][j] * inv[cnt[i + 1 ][a[j]]]) % p; f[i + 1 ][j] = (f[i + 1 ][j] + f[i][j] * (cnt[i + 1 ][a[j]] - 1 ) % p * inv[cnt[i + 1 ][a[j]]]) % p; } else f[i + 1 ][j] = (f[i + 1 ][j] + f[i][j]) % p; } if (g[i][j] && j < n) { if (a[j + 1 ] > a[i]) { f[i][j + 1 ] = (f[i][j + 1 ] + g[i][j] * inv[cnt[j + 1 ][a[i]]]) % p; ans = (ans + g[i][j] * inv[cnt[j + 1 ][a[i]]]) % p; g[i][j + 1 ] = (g[i][j + 1 ] + g[i][j] * (cnt[j + 1 ][a[i]] - 1 ) % p * inv[cnt[j + 1 ][a[i]]]) % p; } else g[i][j + 1 ] = (g[i][j + 1 ] + g[i][j]) % p; } } cout << (ans + p) % p << "\n" ; return 0 ; }