先贴个题目链接I-Club

这就是2023年黑龙江CCPC省赛的一个较难题目.

题面的英文质量不佳,简而言之就是有n个俱乐部,有m种奖章,要把奖章分发给这些俱乐部.其中一种奖章可以分发给多个俱乐部,一个俱乐部只能有一种奖章.

参赛选手每到一个俱乐部玩儿一次就可以得到这个俱乐部所对应的那一种奖章,可以重复地玩同一个俱乐部,需要求解的是得到所有奖章的玩的次数的最小期望.


显然我们应该把这些奖章平摊给这些俱乐部,也就是使得每种奖章发配给俱乐部的数量尽量相同.比如n=9,m=3,每种奖章就应该发配给3个俱乐部.这样才会使得期望能够最小.

但是考虑到有n=8,m=3这种无法直接整除的情况,我们可以把奖章分为两类:

  • A类中的一种发配给俱乐部的数量是
  • B类中的一种发配给俱乐部的数量是

因此,根据m,n恒定的原则,可以列出以下方程,来计算A类奖章的数量和B类奖章的数量.

解得:

也能够很容易地得出要在玩耍时得到A类中某一种奖章的概率是

同理,在玩耍时得到B类中某一种奖章的概率是


为了求解这一问题,需要使用期望DP

的含义是A类奖章还差种就可以选完,B类奖章还差种就可以选完的情况下需要进行的游戏次数.

那么易得,毕竟奖章都拿全了,就不需要再玩了.

dp递推方程是:

答案就是,就是奖章一个也没拿的情况.

下面给出解释:

试想一下: 假如这时候状态为A类奖章还差种就可以选完,B类奖章还差种就可以选完,而你现在决定要在某个俱乐部玩一把,则有三种情况:

  1. 你玩到了新的A类奖章:概率为(拿到某一种A类奖章的概率是,此时还剩下种A类的还没选),得到了之后的玩的次数将会是,算上这一次,则是
  2. 同理,如果玩到了新的B类奖章:概率为,玩的次数是
  3. 压根没选到新的:概率为,玩的次数是

最后对这个dp方程进行化简,得到:


下面是AC代码: 需要注意判断一下边界条件,其他地方基本上没有坑点,属于是把DP方程想明白就能轻松过题.

#include <bits/stdc++.h>
 
using namespace std;
 
const int maxn = 1e5 + 5;
const int maxm = 5005;
 
int main()
{
    vector<vector<double>> dp;
    int n, m;
    cin >> n >> m;
    int A = n + m - m * ceil((double) n / m);
    int B = m - A;
    dp.resize(A + 1);
    for (auto &elem: dp)
        elem.resize(B + 1);
    double p = ceil((double) n / (double) m) / n;
    double q = (ceil((double) n / (double) m) - 1) / n;
    for (int i = 0; i <= A; i++)
    {
        for (int j = 0; j <= B; j++)
        {
            if (i == 0 && j != 0)
                dp[i][j] = q * j / (p * i + q * j) * dp[i][j - 1] + 1 / (p * i + q * j);
            else if (i != 0 && j == 0)
                dp[i][j] = p * i / (p * i + q * j) * dp[i - 1][j] + 1 / (p * i + q * j);
            else if (i == 0 && j == 0)
                dp[i][j] = 0;
            else
                dp[i][j] = p * i / (p * i + q * j) * dp[i - 1][j] + q * j / (p * i + q * j) * dp[i][j - 1] +
                           1 / (p * i + q * j);
        }
    }
    cout.precision(9);
    cout.setf(ios::fixed);
    cout << dp[A][B] << endl;
    return 0;
}