• 蓝桥杯印章


    更多相似题目
    蓝桥杯算法题解

    1. 蓝桥杯印章

    问题描述

    共有n种图案的印章,每种图案的出现概率相同。小A买了m张印章,求小A集齐n种印章的概率。

    输入格式

    一行两个正整数n和m

    输出格式

    一个实数P表示答案,保留4位小数。

    样例输入

    2 3

    样例输出

    0.7500

    数据要求

    m,n <=30

    题解

    一看数据量就猜测是dp的问题,数学问题的数据量一般很大。如果想到了dp,那么就好办了。

    1. 确定维度和含义,题目就两个数字,直接两维(i, j);含义就按照题目 or 子问题的思路去思考。
      • (i, j)表示买了i枚印章,有j种的概率。
    2. 确定子问题,也就是递推式,也就是划分。这里根据最后一次买的印章种类进行划分。
      1. 如果最后一次买的是新种的印章, f(i, j) = f(i - 1, j - 1) * (n - (j - 1) / n)
      2. 如果最后一次买的是旧种类的印章, f(i, j - 1) = f(i - 1, j - 1) * ((j - 1) / n)
      3. 上面的式子2需要进行变形,因为我需要求解的是f(i, j), 那么f(i, j) = (i - 1, j) * (j / n)。
    3. 可以看到上面的划分是不重不漏的,最后的f(i, j)就是式1和2的和。相比于直接想到,f(i - 1, j)这里使用背包dp的思维更加容易。
    4. 最后确定边界。f(1, 1) = 1, 其他等于0。表示买一张有一种的概率是1。

    代码

    • 使用滚动数组优化
    package lanqiao.imporve;
    
    import java.util.Arrays;
    import java.util.Scanner;
    
    /**
     * @author: Zekun Fu
     * @date: 2022/11/21 10:38
     * @Description: 印章
     *
     * 1. dp[n][m]:表示买n枚,集齐m种印章的概率
     *
     * 当前这一种是一种新印章的概率 (n - j) / n
     * 当前这一种是一种旧印章的概率 (j) / n
     *
     * 1. dp[i][j - 1] = dp[i - 1][j - 1] * ((j - 1) / n)
     * -> dp[i][j] = dp[i - 1][j] * (j / n)
     *
     *  2. dp[i][j] = dp[i - 1][j - 1] * ((n - j + 1) / n)
     *
     *  综上
     *  dp[i][j] = dp[i - 1][j] * (j / n) + dp[i - 1][j - 1] * ((n - j + 1) / n)
     *
     *  每一种印章有两种选择
     *  1. 抽到新的
     *  2. 抽到旧的
     *
     * 可以大体看作是背包模型。
     *
     * 2 3的时候
     *
     * i == 1的时候
     * 0 : 0
     * 1 : 1
     *
     * i == 2的时候
     * 1 : 1 * 0.5
     * 2 : 0.5
     *
     * i 等于3的时候
     * 1 = 1
     * 2 = 0.5 + 0.5 * 0.5 = 0.75
     *
     *
     */
    public class YinZhang {
        public static void main(String[] args) {
            Scanner sc = new Scanner(System.in);
            int n = sc.nextInt(), m = sc.nextInt();
            double[] dp = new double[n + 1];
            Arrays.fill(dp, 0);
            dp[1] = 1;
            double d = (double)n;
            for (int i = 2; i <= m; i++) {
                double[] pre = Arrays.copyOf(dp, n + 1);
                for (int j = 1; j <= Math.min(n, i); j++) {
                    dp[j] = pre[j - 1] * ((double) (n - j + 1) / d) + pre[j] * ((double)j / d);
                }
            }
            System.out.printf("%.4f", dp[n]);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    cpp代码

    • 没有优化的代码
    #include 
    #include 
    
    using namespace std;
    
    
    // m种印章种
    /*
        f[i][j]:表示从i种印章中恰好选择j种印章的概率
        f[m][n] = f[m - 1][n - 1]
    
        1. i种印章中含有j种印章的第一种划分
            当前选择的这个是已经选择过的印章
            f[i - 1][j] * j/n
        2. 当前选择的是一种新的印章
            f[ - 1][j - 1] * (n - j + 1) / n;
    
        3. 边界条件
            i = 1的时候,f[1][j] = 1 / n;
            i < j的时候,等于0
    
    
    */
    
    const int N =1005;
    double f[N][N];
    
    int main() {
        int m, n;
        cin >> n >> m;
    
        f[1][1] = 1.0;
        for (int i = 2; i <= m; i++) {
            for (int j = 1; j <= i; j++) {
                f[i][j] = f[i - 1][j - 1] * (double)(n - j + 1) / n + f[i - 1][j] * (double) j / n;
                //cout << f[i - 1][j] * (double) j / n << endl;
            }
        }
    
        printf("%.4llf\n", f[m][n]);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 使用滚动数组优化
    #include 
    #include 
    #include 
    
    using namespace std;
    
    
    // m种印章种
    /*
        f[i][j]:表示从i种印章中恰好选择j种印章的概率
        f[m][n] = f[m - 1][n - 1]
    
        1. i种印章中含有j种印章的第一种划分
            当前选择的这个是已经选择过的印章
            f[i - 1][j] * j/n
        2. 当前选择的是一种新的印章
            f[ - 1][j - 1] * (n - j + 1) / n;
    
        3. 边界条件
            i = 1的时候,f[1][j] = 1;
            i < j的时候,等于0
    
    
    */
    
    const int N = 25;
    double f[2][N];
    
    int main() {
        int m, n;
        cin >> n >> m;
    
        f[1][1] = 1.0;
        for (int i = 2; i <= m; i++) {
            for (int j = 1; j <= i; j++) {
                f[i & 1][j] = f[(i - 1) & 1][j - 1] * (double)(n - j + 1) / n + f[(i - 1) & 1][j] * (double) j / n;
                //cout << f[i - 1][j] * (double) j / n << endl;
            }
        }
    
        printf("%.4llf\n", f[m & 1][n]);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
  • 相关阅读:
    go mod tidy 报错:x509: certificate signed by unknown authority 最佳实践
    k8s上线Java项目文件导出异常总结
    【Datawhale组队学习:Sora原理与技术实战】使用KAN-TTS合成女生沪语音频
    新手快速上手IDEA【常用快捷键】
    nrm、node-sass安装问题、nvm
    Aspose.Words使用教程之表的合并与拆分
    我的AIGC部署实践03
    MIT 6.NULL The Missing Semester of Your CS Education(1)
    打破数据孤岛,推动销售与财务协同升级!
    工作7年收集到的git命令
  • 原文地址:https://blog.csdn.net/fuzekun/article/details/127961820