• 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;
    }
    
  • 相关阅读:
    CentOS 6.3用ssh无密码登陆其它主机
    堆排序算法以及JAVA实现
    TaskTracker发送Heartbeat以及接受HeartbeatResponse
    Linux常用命令总结
    用eclipse将mapreduce程序打成jar包并在命令行执行
    jquery自定义验证方法
    solaris中几个网络经典命令小结
    DWR的简单总结[转]
    java 编码问题 及转换
    DWR学习详解【转】
  • 原文地址:https://www.cnblogs.com/chd-acm/p/10831272.html
Copyright © 2020-2023  润新知