CF1278F Cards

时间:2021-11-14 23:06:04

CF1278F Cards

首先我们知道,一次拿牌的概率是 $ P(i) = frac 1 m $ ,同时权值是1,所以期望就是 $ frac{1} m $,拿 $ n $ 次牌贡献是独立的,就是 $ frac n m $。

但是我们要算的是 $ k $ 次方的期望,众所周知期望的二次方不等于二次方的期望。我们考虑 $ E $ 的意义,$ E $ 在这一次拿到 Joker 的 $ frac 1 m $ 的概率下是 1 ,其他情况是0。则 $ E^2 $ 就是随机两次,这两次都是 1 的情况下是 1 ,其他情况是0。

我们把这 $ n $ 次是否抓到 Joker 的 0/1 写成一个序列,所以知道最后统计的答案,就是所有的长度为 $ k $ 的有序子序列(可以是 $ A_3,A_2,A_2 $ 这种的 ),它做出贡献的前提就是这个子序列的所有随机变量都去到 1 了。

接着考虑,如果两个序列的位置种类数一致,那么它们出现的概率是相同的。如果知道这些位置都是 Joker ,那么这些位置组成的所有序列都会出现。

所以考虑一个 dp ,$ dp[i][j] $ 表示当前在选择第 $ i $ 个位置,到达这个位置时已经有 $ j $ 个不同的位置出现了。那么 $ sum dp[k][i] times frac{1}{m^{i}} $ 就是答案,因为有 $ frac{1}{m^i} $ 的概率这 $ i $ 个钦定的元素位置都是 Joker,这样带来的权值就是方案数。然后考虑这个 dp 的递推,这是很轻松的:
[ dp[i][j] = dp[i-1][j] times j dp[i-1][j-1] times (n-j 1) ]
就是考虑第 $ i $ 个位置是选择前 $ j $ 个之一还是新选择一种。

代码很简单:

#include "algorithm"
#include "iostream"
#include "cstring"
#include "cstdio"
using namespace std;
#define MAXN 5006
#define P 998244353
int n , m , k;
int dp[MAXN][MAXN];
int Pow( int a , int b ) {
    int cur = a % P , ans = 1;
    while( b ) {
        if( b & 1 ) ans = 1ll * ans * cur % P;
        cur = 1ll * cur * cur % P , b >>= 1;
    }
    return ans;
}
int main( ) {
    cin >> n >> m >> k;
    dp[0][0] = 1;
    for( int i = 1 ; i <= k ;    i ) {
        for (int j = 1; j <= i;   j)
            dp[i][j] = ( 1ll * dp[i-1][j] * j % P   1ll * dp[i-1][j-1] * ( n - j   1 ) % P ) % P;
    }
    int res = 0 , cur = 1 , p = Pow( m , P - 2 );
    for( int i = 0 ; i <= k ;    i )
        ( res  = 1ll * dp[k][i] * cur % P ) %= P , cur = 1ll * cur * p % P;
    cout << res << endl;
}