CF-1156F Card Bag

题意:从包中等概率抽取一个卡片,卡片上面有一个数字。不放回抽取,然后从第二次抽取开始,假设当前抽到的数字是x,上一次抽到的数字是y。那么:

  • if x<y, the game ends and you lose;
  • if x=y, the game ends and you win;
  • if x>y, the game continues.

然后求win的概率。

分析:考虑每个数字的贡献,只有个数大于等于2时才会有贡献。枚举这个数字为最后抽取的数字,那么在之前抽取到的数字只会比这个小,而且严格递增有序,假设当前数字为x,那么相当于是从前x-1个数字里面抽不重复的若干个,这个可以看作是一个背包。d[i][j]表示从前 i 个里面抽取 j 个的方案数是多少个。然后抽取 当前结尾数字x时,设x的个数是num[x]个,那么根据排列组合的知识可以知道方案数是num[x] * (num[x]-1),然后我们现在共抽取了2+j 个卡片,赢得了比赛,但是计算总概率时,我们的计算方法是:(赢得的方案总方案数赢得的方案 div 总方案数),因为总方案是 n!,相当于把 n 个卡片每一个的可能都列了进去,所以当我们抽取了 2+j 个卡片赢得了比赛时,我们不能忽略后面 n-2-j个卡片的顺序。

综上,数字x的贡献是

[sum[x] = num[x]*(num[x]-1)*Sigma_{j=0}^{x-1}d[x-1][j] * (n-2-j)! ]

求出每个数字的贡献,就得到了赢得的方案数,然后除以(n!) 即可。注意所有的操作都是在取模操作下,所以除法运算要计算逆元。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5005;
const int mod = 998244353;
int n,a[N];
ll num[N],fac[N],dp[N][N];
ll quick_mod(ll a,ll b){
    ll res = 1;
    for(;b;b>>=1){
        if(b&1)res = res * a % mod;
        a = a * a % mod;
    }
    return res;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        num[a[i]]++;
    }
    fac[0] = 1;
    for(int i=1;i<=5004;i++)fac[i] = fac[i-1] * i % mod;
    dp[0][0] = 1;
    for(int i=1;i<=n;i++){
        dp[i][0] = 1;
        for(int j=1;j<=i;j++){
            dp[i][j] = (dp[i-1][j] + dp[i-1][j-1] * num[i])%mod;
        }
    }
    ll res = 0;
    for(int i=1;i<=n;i++){
        if(num[i] >= 2){
            for(int j=i-1;j >= 0;j--){
                res = (res + dp[i-1][j] * num[i] %mod * (num[i] - 1) %mod * fac[n-j-2] %mod )%mod;
            }
        }
    }
    printf("%lld
",res * quick_mod(fac[n],mod-2)%mod);
    return 0;
}

官方题解方法

d[i][j]表示抽取了 j 个卡片,最后抽取的是数字为 i 的卡片时可以赢得的概率。如何计算d[i][j] 当然是把从 [i][j]状态转移出去的状态(具体转移到哪个是等概率的)的概率求和就可以了。

  • 第一种转移显然是继续拿一个 i 。然后这种方案赢得的概率是 ({cnt_i-1 over n-j})
  • 第二种是拿数字大于 i 的(注意,一次转移只能拿一个)。所以这种方案赢得的概率是(Sigma_{k=i+1}^n{({cnt_k over n-j} * d[k][j+1])})

所以(d[i][j] = {1over n-j} * ((cnt_i-1) + Sigma_{k=i+1}^n(cnt_k * d[k][j+1])))

所以从后往前遍历,用一个数组维护(Sigma_{i=x}^n(cnt_i*d[i][j])) 即可

温馨提示:公式很好懂,但是逆元你真的会用了吗?

#include<bits/stdc++.h>

using namespace std;

const int MOD = 998244353;
const int N = 5005;
//注意a是引用
void upd(int &a, int b){
    a += b;
    a %= MOD;
}
//防止爆int,然后又不用ll,所以用这个
int mul(int a, int b){
    return (a * 1LL * b) % MOD;
}
//快速幂
int bp(int a, int n){
    int res = 1;
    for(; n > 0; n >>= 1){
        if(n & 1) res = mul(res, a);
        a = mul(a, a);
    }
    return res;
}

int getInv(int a){
    int ia = bp(a, MOD - 2);
    assert(mul(a, ia) == 1);
    return ia;
}

int n;
int cnt[N];
int dp[N][N];
int sum[N][N];
int inv[N];//存逆元

int main(){
    for(int i = 1; i < N; ++i)
        inv[i] = getInv(i);
        
    cin >> n;
    for(int i = 0; i < n; ++i){
        int x;
        cin >> x;
        ++cnt[x];
    }
    cnt[0] = 1;
    //从后往前
    for(int x = n; x >= 0; --x)
        for(int y = n; y >= 0; --y){
            if(cnt[x] == 0){
                upd(sum[x][y], sum[x + 1][y]);
                continue;
            }
            int s = n - y;
            if(s <= 0){
                upd(sum[x][y], sum[x + 1][y]);
                continue;
            }
            
            upd(dp[x][y], mul(cnt[x] - 1, inv[s]));//加上贡献的第一项
            upd(dp[x][y], mul(sum[x + 1][y + 1], inv[s]));//第二项
            //维护sum[x][y]
            upd(sum[x][y], sum[x + 1][y]);
            upd(sum[x][y], mul(cnt[x], dp[x][y]));
        }
    
    cout << dp[0][0] << endl;
	return 0;
}
原文地址:https://www.cnblogs.com/chd-acm/p/10831272.html