2026牛客暑期多校10 C题思路分享(期望,组合数学,dp,拉格朗日插值)

题意

\(n\) 台机器,每台机器的奖金是 \([0,m]\) 内随机生成的整数,你可以在任意时刻知晓任意一台机器的奖金.

进行 \(k\) 轮游戏,第 \(j\) 轮你会拿走奖金第 \(a_j\) 大的机器的所有奖金,随后该台机器的奖金重置为 \([0,m]\) 内一个随机整数.

求拿到奖金总和的期望,模 \(10^9+7\).

\(1\le m,k \le 300\)\(1\le m \le 10^9\).

思路

记奖金总和为 \(S\),根据期望的线性性:

\[\mathbb{E}[S] = \sum_{j=1}^{k}{\mathbb{E}[X_j]} \]

根据尾和公式:

\[\mathbb{E}[S] = \sum_{j=1}^{k}{\sum_{i=1}^{m}{\Pr(X_j\ge i)}} \]

交换求和:

\[\mathbb{E}[S] = \sum_{i=1}^{m}{\sum_{j=1}^{k}{\Pr(X_j\ge i)}} \]

\(F(i) = \sum_{j=1}^{k}{\Pr(x_j \ge i)}\),因此:

\[\mathbb{E}[S] = \sum_{i=1}^{m}{F(i)} \]

考虑求解 \(F(i)\).

固定 \(i\) 后,只需讨论奖金与 \(i\) 的相对关系即可,令 \(f_{i,j,c}\) 表示第 \(j\) 轮结束后恰好有 \(c\) 台机器奖金不小于 \(i\) 的概率.

每轮开始前,若 \(c\ge a_j\),则该轮获得的奖金不小于 \(i\),因此:

\[F(i) = \sum_{j=0}^{k-1}{\sum_{c=a_{j+1}}^{n}{f_{i,j,k}}} \]

令机器重置后,新奖金不小于 \(i\) 的概率为 \(p_i\),小于 \(i\) 概率为 \(q_i\),则:

\[\begin{cases} p_i = \frac{m-i+1}{m+1} \\ q_i = \frac{i}{m+1} \end{cases} \]

\(f_{i,j,c}\) 可以通过简单的 \(dp\) 得到.

观察 \(dp\) 的初始化:

\[f_{i,0,c} = \binom{n}{c}p_i^c \cdot q_i^{n-c} \]

以及每次转移都是乘上 \(p_i\)\(q_i\),发现 \(f_{i,j,c}\) 是关于 \(i\) 最多 \(n+k-1\) 次的多项式,而 \(F(i)\) 是若干个 \(f_{i,j,c}\) 的和当然也是关于 \(i\) 最多 \(n+k-1\) 次的多项式.

\(G(t) = \sum_{i=1}^{t}{F(i)}\)\(n+k-1\) 次多项式的前缀和,因此 \(G(t)\)\(n+k\) 次多项式,通过 \(dp\) 求出 \(G(t)\)\(n+k+1\) 项后拉格朗日插值求 \(G(m)\) 即可.

时间复杂度 \(\mathcal{O}(nk(n+k))\).

代码

//author:kzssCCC

#include <bits/stdc++.h>
using namespace std;
using ll = long long;

template<int MOD>
struct modint {
    int val;
    
    modint() : val(0) {}
    modint(long long v) {
        val = v % MOD;
        if (val < 0) val += MOD;
    }
    
    modint& operator++() { val = (val + 1 == MOD ? 0 : val + 1); return *this; }
    modint& operator--() { val = (val == 0 ? MOD - 1 : val - 1); return *this; }
    modint operator++(int) { modint res = *this; ++*this; return res; }
    modint operator--(int) { modint res = *this; --*this; return res; }
    
    modint& operator+=(const modint& o) { val += o.val; if (val >= MOD) val -= MOD; return *this; }
    modint& operator-=(const modint& o) { val -= o.val; if (val < 0) val += MOD; return *this; }
    modint& operator*=(const modint& o) { val = 1LL * val * o.val % MOD; return *this; }
    modint& operator/=(const modint& o) { return *this *= o.inv(); }
    
    friend modint operator+(modint a, const modint& b) { return a += b; }
    friend modint operator-(modint a, const modint& b) { return a -= b; }
    friend modint operator*(modint a, const modint& b) { return a *= b; }
    friend modint operator/(modint a, const modint& b) { return a /= b; }

    friend bool operator==(const modint& a, const modint& b) { return a.val == b.val; }
    friend bool operator!=(const modint& a, const modint& b) { return a.val != b.val; }
    modint operator-() const { modint res = *this; res.val = (res.val == 0 ? 0 : MOD - res.val); return res;};
    modint operator+() const { return *this; };
    
    modint qpow(long long p) const {
        modint res = 1, a = *this;
        while (p > 0) {
            if (p & 1) res *= a;
            a *= a;
            p >>= 1;
        }
        return res;
    }

    modint inv() const {
        return qpow(MOD - 2);
    }
    
    friend std::ostream& operator<<(std::ostream& os, const modint& m) { return os << m.val; }
    friend std::istream& operator>>(std::istream& is, modint& m) { long long v; is >> v; m = modint(v); return is; }
};
using Z = modint<(int)1e9+7>;

struct binom{
    int n;
    vector<Z> fac,ifac;

    binom(int _n){
        n = _n;
        fac.assign(n+1,0);
        ifac.assign(n+1,0);
        
        fac[0] = 1;
        for (int i=1;i<=n;i++){
            fac[i] = fac[i-1]*i;
        }       
        ifac[n] = fac[n].inv();
        for (int i=n-1;i>=0;i--){
            ifac[i] = ifac[i+1]*(i+1);
        }
    }

    Z C(int a,int b){
        if (a<0 || a>n || b<0 || b>n || a<b) return 0;
        return fac[a]*ifac[b]*ifac[a-b];
    }

    Z A(int a,int b){
        if (a<0 || a>n || b<0 || b>n || a<b) return 0;
        return fac[a]*ifac[a-b];
    }
};

binom bn(300);

void solve(){
    int n,m,k;
    cin >> n >> m >> k;

    vector<int> a(k+1);
    for (int i=1;i<=k;i++){
        cin >> a[i];
    }

    vector<Z> F(n+k+2);
    for (int i=1;i<=n+k+1;i++){
        vector<Z> dp(n+1);
        Z p = Z(m-i+1)/(m+1);
        Z q = Z(i)/(m+1);

        vector<Z> facp(n+1,1),facq(n+1,1);
        for (int i=1;i<=n;i++){
            facp[i] = facp[i-1]*p;
            facq[i] = facq[i-1]*q;
        }

        for (int c=0;c<=n;c++){
            dp[c] = bn.C(n,c)*facp[c]*facq[n-c];
        }

        for (int j=1;j<=k;j++){
            vector<Z> ndp(n+1);
            for (int c=0;c<=n;c++){
                if (c>=a[j]){
                    ndp[c] += dp[c]*p;
                    if (c-1>=0){
                        ndp[c-1] += dp[c]*q;
                    }
                }
                else{
                    if (c+1<=n){
                        ndp[c+1] += dp[c]*p;
                    }
                    ndp[c] += dp[c]*q;
                }
            }
  
            for (int c=a[j];c<=n;c++){
                F[i] += dp[c];
            }
            dp = ndp;
        }  
    }

    vector<Z> G(n+k+2);
    for (int i=1;i<=n+k+1;i++){
        G[i] = G[i-1]+F[i];
    }

    Z res = 0;
    for (int i=1;i<=n+k+1;i++){
        Z cur = 1;
        for (int j=1;j<=n+k+1;j++){
            if (i==j) continue;
            cur *= Z(m-j)/(i-j);
        }
        cur *= G[i];
        res += cur;
    }

    cout << res << '\n';
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    int t = 1;
    // cin >> t;
    while (t--) solve();

    return 0;
}
posted @ 2026-08-19 21:45  kzssCCC  阅读(6)  评论(0)    收藏  举报